├── .coveragerc ├── .gitignore ├── .travis.yml ├── COPYING ├── LICENSE ├── MANIFEST.in ├── README.rst ├── bioformats ├── __init__.py ├── formatreader.py ├── formatwriter.py ├── jars │ └── bioformats_package.jar ├── logback.py ├── metadatatools.py ├── noseplugin.py └── omexml.py ├── demo └── demo.py ├── docs ├── Makefile ├── conf.py ├── index.rst └── make.bat ├── setup.cfg ├── setup.py └── tests ├── __init__.py ├── conftest.py ├── resources ├── Channel1-01-A-01.tif ├── groupfiles.xml ├── logback.properties └── tiff.xml ├── test_formatreader.py ├── test_formatwriter.py ├── test_load_using_bioformats.py └── test_omexml.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | erase = True 4 | package = bioformats 5 | [report] 6 | exclude_lines = 7 | # Ignore continue statement in code as it can't be detected as covered 8 | # due to an optimization by the Python interpreter. See coverage issue 9 | # ( https://bitbucket.org/ned/coveragepy/issue/198/continue-marked-as-not-covered ) 10 | # and Python issue ( http://bugs.python.org/issue2506 ). 11 | continue 12 | 13 | # Ignore coverage of code that requires the module to be executed. 14 | if __name__ == .__main__.: 15 | omit = 16 | */python?.?/* 17 | */site-packages/* 18 | *tests/* 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /build 3 | /docs/_build 4 | /python_bioformats.egg-info 5 | /dist 6 | \.eggs/ 7 | \.idea/ 8 | venv 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - 3.8 4 | sudo: false 5 | notifications: 6 | email: false 7 | install: 8 | - pip install --upgrade pip wheel 9 | - pip install --upgrade numpy 10 | - pip install sphinx 11 | - pip install --upgrade .[test] 12 | script: 13 | - pytest 14 | - cd docs && make html && cd .. 15 | deploy: 16 | provider: pypi 17 | user: aether 18 | password: 19 | secure: Z88A/qARvvwFdogCnwiybg9g8+FTiJmTKh4rkfoOkFJP02dirWC3lDf8ZEUGo8Uk9QgO+M9BhcJR6vy7STGHYuVF+xC3lz9nuIQj39JkGfHOdy2aUOwiNQLES6jVGEzhJdDv4mJ5Wg481m0ZFGFxzjnAkpVBR2wxCyN80hcOXPc= 20 | distributions: "sdist bdist_wheel" 21 | on: 22 | python: 3.8 23 | tags: true 24 | repo: CellProfiler/python-bioformats 25 | skip_cleanup: true 26 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 2, June 1991 4 | 5 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 6 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The licenses for most software are designed to take away your 13 | freedom to share and change it. By contrast, the GNU General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. This 16 | General Public License applies to most of the Free Software 17 | Foundation's software and to any other program whose authors commit to 18 | using it. (Some other Free Software Foundation software is covered by 19 | the GNU Lesser General Public License instead.) You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | this service if you wish), that you receive source code or can get it 26 | if you want it, that you can change the software or use pieces of it 27 | in new free programs; and that you know you can do these things. 28 | 29 | To protect your rights, we need to make restrictions that forbid 30 | anyone to deny you these rights or to ask you to surrender the rights. 31 | These restrictions translate to certain responsibilities for you if you 32 | distribute copies of the software, or if you modify it. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must give the recipients all the rights that 36 | you have. You must make sure that they, too, receive or can get the 37 | source code. And you must show them these terms so they know their 38 | rights. 39 | 40 | We protect your rights with two steps: (1) copyright the software, and 41 | (2) offer you this license which gives you legal permission to copy, 42 | distribute and/or modify the software. 43 | 44 | Also, for each author's protection and ours, we want to make certain 45 | that everyone understands that there is no warranty for this free 46 | software. If the software is modified by someone else and passed on, we 47 | want its recipients to know that what they have is not the original, so 48 | that any problems introduced by others will not reflect on the original 49 | authors' reputations. 50 | 51 | Finally, any free program is threatened constantly by software 52 | patents. We wish to avoid the danger that redistributors of a free 53 | program will individually obtain patent licenses, in effect making the 54 | program proprietary. To prevent this, we have made it clear that any 55 | patent must be licensed for everyone's free use or not licensed at all. 56 | 57 | The precise terms and conditions for copying, distribution and 58 | modification follow. 59 | 60 | GNU GENERAL PUBLIC LICENSE 61 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 62 | 63 | 0. This License applies to any program or other work which contains 64 | a notice placed by the copyright holder saying it may be distributed 65 | under the terms of this General Public License. The "Program", below, 66 | refers to any such program or work, and a "work based on the Program" 67 | means either the Program or any derivative work under copyright law: 68 | that is to say, a work containing the Program or a portion of it, 69 | either verbatim or with modifications and/or translated into another 70 | language. (Hereinafter, translation is included without limitation in 71 | the term "modification".) Each licensee is addressed as "you". 72 | 73 | Activities other than copying, distribution and modification are not 74 | covered by this License; they are outside its scope. The act of 75 | running the Program is not restricted, and the output from the Program 76 | is covered only if its contents constitute a work based on the 77 | Program (independent of having been made by running the Program). 78 | Whether that is true depends on what the Program does. 79 | 80 | 1. You may copy and distribute verbatim copies of the Program's 81 | source code as you receive it, in any medium, provided that you 82 | conspicuously and appropriately publish on each copy an appropriate 83 | copyright notice and disclaimer of warranty; keep intact all the 84 | notices that refer to this License and to the absence of any warranty; 85 | and give any other recipients of the Program a copy of this License 86 | along with the Program. 87 | 88 | You may charge a fee for the physical act of transferring a copy, and 89 | you may at your option offer warranty protection in exchange for a fee. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion 92 | of it, thus forming a work based on the Program, and copy and 93 | distribute such modifications or work under the terms of Section 1 94 | above, provided that you also meet all of these conditions: 95 | 96 | a) You must cause the modified files to carry prominent notices 97 | stating that you changed the files and the date of any change. 98 | 99 | b) You must cause any work that you distribute or publish, that in 100 | whole or in part contains or is derived from the Program or any 101 | part thereof, to be licensed as a whole at no charge to all third 102 | parties under the terms of this License. 103 | 104 | c) If the modified program normally reads commands interactively 105 | when run, you must cause it, when started running for such 106 | interactive use in the most ordinary way, to print or display an 107 | announcement including an appropriate copyright notice and a 108 | notice that there is no warranty (or else, saying that you provide 109 | a warranty) and that users may redistribute the program under 110 | these conditions, and telling the user how to view a copy of this 111 | License. (Exception: if the Program itself is interactive but 112 | does not normally print such an announcement, your work based on 113 | the Program is not required to print an announcement.) 114 | 115 | These requirements apply to the modified work as a whole. If 116 | identifiable sections of that work are not derived from the Program, 117 | and can be reasonably considered independent and separate works in 118 | themselves, then this License, and its terms, do not apply to those 119 | sections when you distribute them as separate works. But when you 120 | distribute the same sections as part of a whole which is a work based 121 | on the Program, the distribution of the whole must be on the terms of 122 | this License, whose permissions for other licensees extend to the 123 | entire whole, and thus to each and every part regardless of who wrote it. 124 | 125 | Thus, it is not the intent of this section to claim rights or contest 126 | your rights to work written entirely by you; rather, the intent is to 127 | exercise the right to control the distribution of derivative or 128 | collective works based on the Program. 129 | 130 | In addition, mere aggregation of another work not based on the Program 131 | with the Program (or with a work based on the Program) on a volume of 132 | a storage or distribution medium does not bring the other work under 133 | the scope of this License. 134 | 135 | 3. You may copy and distribute the Program (or a work based on it, 136 | under Section 2) in object code or executable form under the terms of 137 | Sections 1 and 2 above provided that you also do one of the following: 138 | 139 | a) Accompany it with the complete corresponding machine-readable 140 | source code, which must be distributed under the terms of Sections 141 | 1 and 2 above on a medium customarily used for software interchange; or, 142 | 143 | b) Accompany it with a written offer, valid for at least three 144 | years, to give any third party, for a charge no more than your 145 | cost of physically performing source distribution, a complete 146 | machine-readable copy of the corresponding source code, to be 147 | distributed under the terms of Sections 1 and 2 above on a medium 148 | customarily used for software interchange; or, 149 | 150 | c) Accompany it with the information you received as to the offer 151 | to distribute corresponding source code. (This alternative is 152 | allowed only for noncommercial distribution and only if you 153 | received the program in object code or executable form with such 154 | an offer, in accord with Subsection b above.) 155 | 156 | The source code for a work means the preferred form of the work for 157 | making modifications to it. For an executable work, complete source 158 | code means all the source code for all modules it contains, plus any 159 | associated interface definition files, plus the scripts used to 160 | control compilation and installation of the executable. However, as a 161 | special exception, the source code distributed need not include 162 | anything that is normally distributed (in either source or binary 163 | form) with the major components (compiler, kernel, and so on) of the 164 | operating system on which the executable runs, unless that component 165 | itself accompanies the executable. 166 | 167 | If distribution of executable or object code is made by offering 168 | access to copy from a designated place, then offering equivalent 169 | access to copy the source code from the same place counts as 170 | distribution of the source code, even though third parties are not 171 | compelled to copy the source along with the object code. 172 | 173 | 4. You may not copy, modify, sublicense, or distribute the Program 174 | except as expressly provided under this License. Any attempt 175 | otherwise to copy, modify, sublicense or distribute the Program is 176 | void, and will automatically terminate your rights under this License. 177 | However, parties who have received copies, or rights, from you under 178 | this License will not have their licenses terminated so long as such 179 | parties remain in full compliance. 180 | 181 | 5. You are not required to accept this License, since you have not 182 | signed it. However, nothing else grants you permission to modify or 183 | distribute the Program or its derivative works. These actions are 184 | prohibited by law if you do not accept this License. Therefore, by 185 | modifying or distributing the Program (or any work based on the 186 | Program), you indicate your acceptance of this License to do so, and 187 | all its terms and conditions for copying, distributing or modifying 188 | the Program or works based on it. 189 | 190 | 6. Each time you redistribute the Program (or any work based on the 191 | Program), the recipient automatically receives a license from the 192 | original licensor to copy, distribute or modify the Program subject to 193 | these terms and conditions. You may not impose any further 194 | restrictions on the recipients' exercise of the rights granted herein. 195 | You are not responsible for enforcing compliance by third parties to 196 | this License. 197 | 198 | 7. If, as a consequence of a court judgment or allegation of patent 199 | infringement or for any other reason (not limited to patent issues), 200 | conditions are imposed on you (whether by court order, agreement or 201 | otherwise) that contradict the conditions of this License, they do not 202 | excuse you from the conditions of this License. If you cannot 203 | distribute so as to satisfy simultaneously your obligations under this 204 | License and any other pertinent obligations, then as a consequence you 205 | may not distribute the Program at all. For example, if a patent 206 | license would not permit royalty-free redistribution of the Program by 207 | all those who receive copies directly or indirectly through you, then 208 | the only way you could satisfy both it and this License would be to 209 | refrain entirely from distribution of the Program. 210 | 211 | If any portion of this section is held invalid or unenforceable under 212 | any particular circumstance, the balance of the section is intended to 213 | apply and the section as a whole is intended to apply in other 214 | circumstances. 215 | 216 | It is not the purpose of this section to induce you to infringe any 217 | patents or other property right claims or to contest validity of any 218 | such claims; this section has the sole purpose of protecting the 219 | integrity of the free software distribution system, which is 220 | implemented by public license practices. Many people have made 221 | generous contributions to the wide range of software distributed 222 | through that system in reliance on consistent application of that 223 | system; it is up to the author/donor to decide if he or she is willing 224 | to distribute software through any other system and a licensee cannot 225 | impose that choice. 226 | 227 | This section is intended to make thoroughly clear what is believed to 228 | be a consequence of the rest of this License. 229 | 230 | 8. If the distribution and/or use of the Program is restricted in 231 | certain countries either by patents or by copyrighted interfaces, the 232 | original copyright holder who places the Program under this License 233 | may add an explicit geographical distribution limitation excluding 234 | those countries, so that distribution is permitted only in or among 235 | countries not thus excluded. In such case, this License incorporates 236 | the limitation as if written in the body of this License. 237 | 238 | 9. The Free Software Foundation may publish revised and/or new versions 239 | of the General Public License from time to time. Such new versions will 240 | be similar in spirit to the present version, but may differ in detail to 241 | address new problems or concerns. 242 | 243 | Each version is given a distinguishing version number. If the Program 244 | specifies a version number of this License which applies to it and "any 245 | later version", you have the option of following the terms and conditions 246 | either of that version or of any later version published by the Free 247 | Software Foundation. If the Program does not specify a version number of 248 | this License, you may choose any version ever published by the Free Software 249 | Foundation. 250 | 251 | 10. If you wish to incorporate parts of the Program into other free 252 | programs whose distribution conditions are different, write to the author 253 | to ask for permission. For software which is copyrighted by the Free 254 | Software Foundation, write to the Free Software Foundation; we sometimes 255 | make exceptions for this. Our decision will be guided by the two goals 256 | of preserving the free status of all derivatives of our free software and 257 | of promoting the sharing and reuse of software generally. 258 | 259 | NO WARRANTY 260 | 261 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 262 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 263 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 264 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 265 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 266 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 267 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 268 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 269 | REPAIR OR CORRECTION. 270 | 271 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 272 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 273 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 274 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 275 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 276 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 277 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 278 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 279 | POSSIBILITY OF SUCH DAMAGES. 280 | 281 | END OF TERMS AND CONDITIONS 282 | 283 | How to Apply These Terms to Your New Programs 284 | 285 | If you develop a new program, and you want it to be of the greatest 286 | possible use to the public, the best way to achieve this is to make it 287 | free software which everyone can redistribute and change under these terms. 288 | 289 | To do so, attach the following notices to the program. It is safest 290 | to attach them to the start of each source file to most effectively 291 | convey the exclusion of warranty; and each file should have at least 292 | the "copyright" line and a pointer to where the full notice is found. 293 | 294 | 295 | Copyright (C) 296 | 297 | This program is free software; you can redistribute it and/or modify 298 | it under the terms of the GNU General Public License as published by 299 | the Free Software Foundation; either version 2 of the License, or 300 | (at your option) any later version. 301 | 302 | This program is distributed in the hope that it will be useful, 303 | but WITHOUT ANY WARRANTY; without even the implied warranty of 304 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 305 | GNU General Public License for more details. 306 | 307 | You should have received a copy of the GNU General Public License along 308 | with this program; if not, write to the Free Software Foundation, Inc., 309 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 310 | 311 | Also add information on how to contact you by electronic and paper mail. 312 | 313 | If the program is interactive, make it output a short notice like this 314 | when it starts in an interactive mode: 315 | 316 | Gnomovision version 69, Copyright (C) year name of author 317 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 318 | This is free software, and you are welcome to redistribute it 319 | under certain conditions; type `show c' for details. 320 | 321 | The hypothetical commands `show w' and `show c' should show the appropriate 322 | parts of the General Public License. Of course, the commands you use may 323 | be called something other than `show w' and `show c'; they could even be 324 | mouse-clicks or menu items--whatever suits your program. 325 | 326 | You should also get your employer (if you work as a programmer) or your 327 | school, if any, to sign a "copyright disclaimer" for the program, if 328 | necessary. Here is a sample; alter the names: 329 | 330 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 331 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 332 | 333 | , 1 April 1989 334 | Ty Coon, President of Vice 335 | 336 | This General Public License does not permit incorporating your program into 337 | proprietary programs. If your program is a subroutine library, you may 338 | consider it more useful to permit linking proprietary applications with the 339 | library. If this is what you want to do, use the GNU Lesser General 340 | Public License instead of this License. 341 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | CellProfiler is licensed under the GNU General Public License version 2 | 2. See the file COPYING for the terms of the GNU GPL license. 3 | 4 | Certain files are licensed under the more permissive BSD license. 5 | This is indicated near the top of the relevant files. The BSD license 6 | is as follows: 7 | 8 | Copyright (c) 2003-2009 Massachusetts Institute of Technology 9 | Copyright (c) 2009-2013 Broad Institute 10 | All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without 13 | modification, are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright 16 | notice, this list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above 19 | copyright notice, this list of conditions and the following 20 | disclaimer in the documentation and/or other materials provided 21 | with the distribution. 22 | 23 | * Neither the name of the Massachusetts Institute of Technology 24 | nor the Broad Institute nor the names of its contributors may be 25 | used to endorse or promote products derived from this software 26 | without specific prior written permission. 27 | 28 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 29 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 30 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 31 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL MASSACHUSETTS 32 | INSTITUTE OF TECHNOLOGY OR THE BROAD INSTITUTE BE LIABLE FOR ANY 33 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 34 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE 35 | GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 36 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 37 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 38 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 39 | ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 40 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include bioformats/tests *.py 2 | recursive-include bioformats/tests *.tif 3 | recursive-include bioformats/tests *.xml 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |Travis CI Status| 2 | 3 | ----------------------------------------------------------------------------------------------------- 4 | 5 | Python-bioformats is a Python wrapper for Bio-Formats, a standalone 6 | Java library for reading and writing life sciences image file 7 | formats. Bio-Formats is capable of parsing both pixels and metadata 8 | for a large number of formats, as well as writing to several 9 | formats. Python-bioformats uses the python-javabridge to start a Java 10 | virtual machine from Python and interact with it. Python-bioformats 11 | was developed for and is used by the cell image analysis software 12 | CellProfiler (cellprofiler.org). While we are gratified that others 13 | outside the CellProfiler team find it useful, we maintain python-bioformats 14 | essentially for the CellProfiler project and **cannot guarantee support 15 | for other users.** Please consider visiting our forum at forum.image.sc 16 | for additional support help. 17 | 18 | PyPI record: https://pypi.python.org/pypi/python-bioformats 19 | 20 | Documentation: http://pythonhosted.org/python-bioformats/ 21 | 22 | GitHub repository: https://github.com/CellProfiler/python-bioformats 23 | 24 | Report bugs here: https://github.com/CellProfiler/python-bioformats/issues 25 | 26 | python-bioformats is licensed under the GNU General Public License 27 | (GPL). Many files are licensed under the more permissive BSD license. 28 | See the accompanying files COPYING and LICENSE for details. 29 | 30 | Copyright (c) 2009-2021 Broad Institute. All rights reserved. 31 | 32 | 33 | .. |Travis CI Status| image:: https://travis-ci.org/CellProfiler/python-bioformats.svg?branch=master 34 | :target: https://travis-ci.org/CellProfiler/python-bioformats 35 | -------------------------------------------------------------------------------- /bioformats/__init__.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | '''Bioformats package - wrapper for loci.bioformats java code 9 | 10 | ''' 11 | 12 | from __future__ import absolute_import, unicode_literals 13 | 14 | try: 15 | from ._version import version as __version__ 16 | except ImportError: 17 | # We're running in a tree that doesn't have a _version.py, so we don't know what our version is. 18 | __version__ = "0.0.0" 19 | 20 | import os.path 21 | import javabridge 22 | from . import formatreader as _formatreader 23 | from . import formatwriter as _formatwriter 24 | 25 | _jars_dir = os.path.join(os.path.dirname(__file__), 'jars') 26 | 27 | JAR_VERSION = '6.5.1' 28 | 29 | JARS = javabridge.JARS + [os.path.realpath(os.path.join(_jars_dir, name + '.jar')) 30 | for name in ['bioformats_package']] 31 | """List of directories, jar files, and zip files that should be added 32 | to the Java virtual machine's class path.""" 33 | 34 | # See http://www.loci.wisc.edu/software/bio-formats 35 | READABLE_FORMATS = ('1sc', '2fl', 'acff', 'afi', 'afm', 'aim', 'al3d', 'ali', 36 | 'am', 'amiramesh', 'apl', 'arf', 'avi', 'bif', 'bin', 'bip', 37 | 'bmp', 'btf', 'c01', 'cfg', 'ch5', 'cif', 'cr2', 'crw', 38 | 'cxd', 'czi', 'dat', 'dcm', 'dib', 'dicom', 'dm2', 'dm3', 39 | 'dm4', 'dti', 'dv', 'eps', 'epsi', 'exp', 'fdf', 'fff', 40 | 'ffr', 'fits', 'flex', 'fli', 'frm', 'gel', 'gif', 'grey', 41 | 'h5', 'hdf', 'hdr', 'hed', 'his', 'htd', 'html', 'hx', 'i2i', 42 | 'ics', 'ids', 'im3', 'img', 'ims', 'inr', 'ipl', 'ipm', 'ipw', 43 | 'j2k', 'jp2', 'jpeg', 'jpf', 'jpg', 'jpk', 'jpx', 'klb', 44 | 'l2d', 'labels', 'lei', 'lif', 'liff', 'lim', 'lms', 'lsm', 45 | 'map', 'mdb', 'mea', 'mnc', 'mng', 'mod', 'mov', 'mrc', 'mrcs', 46 | 'mrw', 'msr', 'mtb', 'mvd2', 'naf', 'nd', 'nd2', 'ndpi', 'ndpis', 47 | 'nef', 'nhdr', 'nii', 'nii.gz', 'nrrd', 'obf', 'obsep', 'oib', 48 | 'oif', 'oir', 'ome', 'ome.btf', 'ome.tf2', 'ome.tf8', 'ome.tif', 49 | 'ome.tiff', 'ome.xml', 'par', 'pbm', 'pcoraw', 'pcx', 'pds', 50 | 'pgm', 'pic', 'pict', 'png', 'pnl', 'ppm', 'pr3', 'ps', 'psd', 51 | 'qptiff', 'r3d', 'raw', 'rcpnl', 'rec', 'res', 'scn', 'sdt', 52 | 'seq', 'sif', 'sld', 'sm2', 'sm3', 'spc', 'spe', 'spi', 'st', 53 | 'stk', 'stp', 'svs', 'sxm', 'tc.', 'tf2', 'tf8', 'tfr', 'tga', 54 | 'tif', 'tiff', 'tnb', 'top', 'txt', 'v', 'vff', 'vms', 'vsi', 55 | 'vws', 'wat', 'wlz', 'wpi', 'xdce', 'xml', 'xqd', 'xqf', 'xv', 56 | 'xys', 'zfp', 'zfr', 'zvi') 57 | 58 | WRITABLE_FORMATS = ('avi', 'eps', 'epsi', 'ics', 'ids', 'jp2', 'jpeg', 'jpg', 59 | 'mov', 'ome', 'ome.tiff', 'png', 'ps', 'tif', 'tiff') 60 | 61 | OMETiffWriter = _formatwriter.make_ome_tiff_writer_class() 62 | ChannelSeparator = _formatreader.make_reader_wrapper_class( 63 | "loci/formats/ChannelSeparator") 64 | 65 | 66 | from .metadatatools import createOMEXMLMetadata as create_ome_xml_metadata 67 | from .metadatatools import wrap_imetadata_object 68 | from . import metadatatools as _metadatatools 69 | PixelType = _metadatatools.make_pixel_type_class() 70 | get_metadata_options = _metadatatools.get_metadata_options 71 | 72 | # Reading images 73 | 74 | ImageReader = _formatreader.ImageReader 75 | load_image = _formatreader.load_using_bioformats 76 | load_image_url = _formatreader.load_using_bioformats_url 77 | 78 | # Cached image readers 79 | 80 | get_image_reader = _formatreader.get_image_reader 81 | release_image_reader = _formatreader.release_image_reader 82 | clear_image_reader_cache = _formatreader.clear_image_reader_cache 83 | 84 | # Metadata 85 | 86 | from .omexml import OMEXML 87 | get_omexml_metadata = _formatreader.get_omexml_metadata 88 | 89 | # Writing images 90 | 91 | write_image = _formatwriter.write_image 92 | 93 | from .omexml import PT_UINT16, PT_UINT8, PT_BIT 94 | 95 | # Omero 96 | 97 | from .formatreader import use_omero_credentials, set_omero_credentials, get_omero_credentials 98 | from .formatreader import set_omero_login_hook, omero_logout, has_omero_packages 99 | from .formatreader import K_OMERO_SERVER, K_OMERO_PORT, K_OMERO_USER, K_OMERO_SESSION_ID,\ 100 | K_OMERO_PASSWORD, K_OMERO_CONFIG_FILE 101 | 102 | from . import omexml 103 | 104 | 105 | if __name__ == "__main__": 106 | # Handy-dandy PyShell for exploring BioFormats / Rhino / ImageJ 107 | import wx.py.PyCrust 108 | 109 | wx.py.PyCrust.main() 110 | javabridge.kill_vm() 111 | -------------------------------------------------------------------------------- /bioformats/formatreader.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | '''formatreader.py - mechanism to wrap a bioformats ReaderWrapper and ImageReader 9 | 10 | Example: 11 | import bioformats.formatreader as biordr 12 | 13 | env = biordr.get_env() 14 | 15 | ChannelSeparator = biordr.make_reader_wrapper_class(env, 'loci/formats/ChannelSeparator') 16 | ImageReader = biordr.make_image_reader_class(env) 17 | 18 | cs = ChannelSeparator(ImageReader('/path/to/file.tif')) 19 | 20 | my_red_image, my_green_image, my_blue_image = \ 21 | [cs.open_bytes(cs.getIndex(0,i,0)) for i in range(3)] 22 | 23 | ''' 24 | 25 | from __future__ import absolute_import, unicode_literals 26 | 27 | __version__ = "$Revision$" 28 | 29 | import logging 30 | logger = logging.getLogger(__name__) 31 | import errno 32 | import numpy as np 33 | import os 34 | import sys 35 | import re 36 | 37 | if sys.version_info.major == 3: 38 | from urllib.request import urlopen, urlparse, url2pathname 39 | from urllib.parse import unquote 40 | else: 41 | from urllib import url2pathname 42 | from urllib2 import urlopen, urlparse, unquote 43 | urlparse = urlparse.urlparse 44 | 45 | import shutil 46 | import tempfile 47 | import traceback 48 | 49 | import javabridge as jutil 50 | import bioformats 51 | from . import metadatatools as metadatatools 52 | import javabridge as javabridge 53 | import boto3 54 | 55 | OMERO_READER_IMPORTED = False 56 | try: 57 | from omero_reader import OmeroReader, OMERO_IMPORTED 58 | from omero_reader.utils import omero_reader_enabled 59 | OMERO_READER_IMPORTED = True 60 | except ImportError: 61 | pass 62 | 63 | K_OMERO_SERVER = "omero_server" 64 | K_OMERO_PORT = "omero_port" 65 | K_OMERO_USER = "omero_user" 66 | K_OMERO_SESSION_ID = "omero_session_id" 67 | K_OMERO_CONFIG_FILE = "omero_config_file" 68 | '''The cleartext password - only used if password is provided on command-line''' 69 | K_OMERO_PASSWORD = "omero_password" 70 | 71 | def make_format_tools_class(): 72 | '''Get a wrapper for the loci/formats/FormatTools class 73 | 74 | The FormatTools class has many of the constants needed by 75 | other classes as statics. 76 | ''' 77 | class FormatTools(object): 78 | '''A wrapper for loci.formats.FormatTools 79 | 80 | See http://hudson.openmicroscopy.org.uk/job/LOCI/javadoc/loci/formats/FormatTools.html 81 | ''' 82 | env = jutil.get_env() 83 | klass = env.find_class('loci/formats/FormatTools') 84 | CAN_GROUP = jutil.get_static_field(klass, 'CAN_GROUP','I') 85 | CANNOT_GROUP = jutil.get_static_field(klass, 'CANNOT_GROUP','I') 86 | DOUBLE = jutil.get_static_field(klass, 'DOUBLE','I') 87 | FLOAT = jutil.get_static_field(klass, 'FLOAT', 'I') 88 | INT16 = jutil.get_static_field(klass, 'INT16', 'I') 89 | INT32 = jutil.get_static_field(klass, 'INT32', 'I') 90 | INT8 = jutil.get_static_field(klass, 'INT8', 'I') 91 | MUST_GROUP = jutil.get_static_field(klass, 'MUST_GROUP', 'I') 92 | UINT16 = jutil.get_static_field(klass, 'UINT16', 'I') 93 | UINT32 = jutil.get_static_field(klass, 'UINT32', 'I') 94 | UINT8 = jutil.get_static_field(klass, 'UINT8', 'I') 95 | 96 | @classmethod 97 | def getPixelTypeString(cls, pixel_type): 98 | return jutil.static_call('loci/formats/FormatTools', 'getPixelTypeString', '(I)Ljava/lang/String;', pixel_type) 99 | 100 | return FormatTools 101 | 102 | def make_iformat_reader_class(): 103 | '''Bind a Java class that implements IFormatReader to a Python class 104 | 105 | Returns a class that implements IFormatReader through calls to the 106 | implemented class passed in. The returned class can be subclassed to 107 | provide additional bindings. 108 | ''' 109 | class IFormatReader(object): 110 | '''A wrapper for loci.formats.IFormatReader 111 | 112 | See http://hudson.openmicroscopy.org.uk/job/LOCI/javadoc/loci/formats/ImageReader.html 113 | ''' 114 | close = jutil.make_method('close','()V', 115 | 'Close the currently open file and free memory') 116 | getDimensionOrder = jutil.make_method('getDimensionOrder', 117 | '()Ljava/lang/String;', 118 | 'Return the dimension order as a five-character string, e.g. "XYCZT"') 119 | getGlobalMetadata = jutil.make_method('getGlobalMetadata', 120 | '()Ljava/util/Hashtable;', 121 | 'Obtains the hashtable containing the global metadata field/value pairs') 122 | getMetadata = getGlobalMetadata 123 | getMetadataValue = jutil.make_method('getMetadataValue', 124 | '(Ljava/lang/String;)' 125 | 'Ljava/lang/Object;', 126 | 'Look up a specific metadata value from the store') 127 | getSeriesMetadata = jutil.make_method('getSeriesMetadata', 128 | '()Ljava/util/Hashtable;', 129 | 'Obtains the hashtable contaning the series metadata field/value pairs') 130 | getSeriesCount = jutil.make_method('getSeriesCount', 131 | '()I', 132 | 'Return the # of image series in the file') 133 | getSeries = jutil.make_method('getSeries', '()I', 134 | 'Return the currently selected image series') 135 | getImageCount = jutil.make_method('getImageCount', 136 | '()I','Determines the number of images in the current file') 137 | getIndex = jutil.make_method('getIndex', '(III)I', 138 | 'Get the plane index given z, c, t') 139 | getRGBChannelCount = jutil.make_method('getRGBChannelCount', 140 | '()I','Gets the number of channels per RGB image (if not RGB, this returns 1') 141 | getSizeC = jutil.make_method('getSizeC', '()I', 142 | 'Get the number of color planes') 143 | getSizeT = jutil.make_method('getSizeT', '()I', 144 | 'Get the number of frames in the image') 145 | getSizeX = jutil.make_method('getSizeX', '()I', 146 | 'Get the image width') 147 | getSizeY = jutil.make_method('getSizeY', '()I', 148 | 'Get the image height') 149 | getSizeZ = jutil.make_method('getSizeZ', '()I', 150 | 'Get the image depth') 151 | getPixelType = jutil.make_method('getPixelType', '()I', 152 | 'Get the pixel type: see FormatTools for types') 153 | isLittleEndian = jutil.make_method('isLittleEndian', 154 | '()Z','Return True if the data is in little endian order') 155 | isRGB = jutil.make_method('isRGB', '()Z', 156 | 'Return True if images in the file are RGB') 157 | isInterleaved = jutil.make_method('isInterleaved', '()Z', 158 | 'Return True if image colors are interleaved within a plane') 159 | isIndexed = jutil.make_method('isIndexed', '()Z', 160 | 'Return True if the raw data is indexes in a lookup table') 161 | openBytes = jutil.make_method('openBytes','(I)[B', 162 | 'Get the specified image plane as a byte array') 163 | openBytesXYWH = jutil.make_method('openBytes','(IIIII)[B', 164 | '''Get the specified image plane as a byte array 165 | 166 | (corresponds to openBytes(int no, int x, int y, int w, int h)) 167 | no - image plane number 168 | x,y - offset into image 169 | w,h - dimensions of image to return''') 170 | setSeries = jutil.make_method('setSeries','(I)V','Set the currently selected image series') 171 | setGroupFiles = jutil.make_method('setGroupFiles', '(Z)V', 172 | 'Force reader to group or not to group files in a multi-file set') 173 | setMetadataStore = jutil.make_method('setMetadataStore', 174 | '(Lloci/formats/meta/MetadataStore;)V', 175 | 'Sets the default metadata store for this reader.') 176 | setMetadataOptions = jutil.make_method('setMetadataOptions', 177 | '(Lloci/formats/in/MetadataOptions;)V', 178 | 'Sets the metadata options used when reading metadata') 179 | isThisTypeS = jutil.make_method( 180 | 'isThisType', 181 | '(Ljava/lang/String;)Z', 182 | 'Return true if the filename might be handled by this reader') 183 | isThisTypeSZ = jutil.make_method( 184 | 'isThisType', 185 | '(Ljava/lang/String;Z)Z', 186 | '''Return true if the named file is handled by this reader. 187 | 188 | filename - name of file 189 | 190 | allowOpen - True if the reader is allowed to open files 191 | when making its determination 192 | ''') 193 | isThisTypeStream = jutil.make_method( 194 | 'isThisType', 195 | '(Lloci/common/RandomAccessInputStream;)Z', 196 | '''Return true if the stream might be parseable by this reader. 197 | 198 | stream - the RandomAccessInputStream to be used to read the file contents 199 | 200 | Note that both isThisTypeS and isThisTypeStream must return true 201 | for the type to truly be handled.''') 202 | def setId(self, path): 203 | '''Set the name of the file''' 204 | jutil.call(self.o, 'setId', 205 | '(Ljava/lang/String;)V', 206 | path) 207 | 208 | getMetadataStore = jutil.make_method('getMetadataStore', '()Lloci/formats/meta/MetadataStore;', 209 | 'Retrieves the current metadata store for this reader.') 210 | get8BitLookupTable = jutil.make_method( 211 | 'get8BitLookupTable', 212 | '()[[B', 'Get a lookup table for 8-bit indexed images') 213 | get16BitLookupTable = jutil.make_method( 214 | 'get16BitLookupTable', 215 | '()[[S', 'Get a lookup table for 16-bit indexed images') 216 | def get_class_name(self): 217 | return jutil.call(jutil.call(self.o, 'getClass', '()Ljava/lang/Class;'), 218 | 'getName', '()Ljava/lang/String;') 219 | 220 | @property 221 | def suffixNecessary(self): 222 | if self.get_class_name() == 'loci.formats.in.JPKReader': 223 | return True; 224 | env = jutil.get_env() 225 | klass = env.get_object_class(self.o) 226 | field_id = env.get_field_id(klass, "suffixNecessary", "Z") 227 | if field_id is None: 228 | return None 229 | return env.get_boolean_field(self.o, field_id) 230 | 231 | @property 232 | def suffixSufficient(self): 233 | if self.get_class_name() == 'loci.formats.in.JPKReader': 234 | return True; 235 | env = jutil.get_env() 236 | klass = env.get_object_class(self.o) 237 | field_id = env.get_field_id(klass, "suffixSufficient", "Z") 238 | if field_id is None: 239 | return None 240 | return env.get_boolean_field(self.o, field_id) 241 | 242 | 243 | return IFormatReader 244 | 245 | def get_class_list(): 246 | '''Return a wrapped instance of loci.formats.ClassList''' 247 | # 248 | # This uses the reader.txt file from inside the jar 249 | # 250 | class ClassList(object): 251 | remove_class = jutil.make_method( 252 | 'removeClass', '(Ljava/lang/Class;)V', 253 | 'Remove the given class from the class list') 254 | add_class = jutil.make_method( 255 | 'addClass', '(Ljava/lang/Class;)V', 256 | 'Add the given class to the back of the class list') 257 | get_classes = jutil.make_method( 258 | 'getClasses', '()[Ljava/lang/Class;', 259 | 'Get the classes in the list as an array') 260 | 261 | def __init__(self): 262 | env = jutil.get_env() 263 | class_name = 'loci/formats/ImageReader' 264 | klass = env.find_class(class_name) 265 | base_klass = env.find_class('loci/formats/IFormatReader') 266 | self.o = jutil.make_instance("loci/formats/ClassList", 267 | "(Ljava/lang/String;" 268 | "Ljava/lang/Class;" # base 269 | "Ljava/lang/Class;)V", # location in jar 270 | "readers.txt", base_klass, klass) 271 | problem_classes = [ 272 | # BDReader will read all .tif files in an experiment if it's 273 | # called to load a .tif. 274 | # 275 | 'loci.formats.in.BDReader', 276 | # 277 | # MRCReader will read .stk files which should be read 278 | # by MetamorphReader 279 | # 280 | 'loci.formats.in.MRCReader' 281 | ] 282 | for problem_class in problem_classes: 283 | # Move to back 284 | klass = jutil.class_for_name(problem_class) 285 | self.remove_class(klass) 286 | self.add_class(klass) 287 | return ClassList() 288 | 289 | 290 | def make_image_reader_class(): 291 | '''Return an image reader class for the given Java environment''' 292 | env = jutil.get_env() 293 | class_name = 'loci/formats/ImageReader' 294 | klass = env.find_class(class_name) 295 | base_klass = env.find_class('loci/formats/IFormatReader') 296 | IFormatReader = make_iformat_reader_class() 297 | class_list = get_class_list() 298 | 299 | class ImageReader(IFormatReader): 300 | new_fn = jutil.make_new(class_name, '(Lloci/formats/ClassList;)V') 301 | def __init__(self): 302 | self.new_fn(class_list.o) 303 | getFormat = jutil.make_method('getFormat', 304 | '()Ljava/lang/String;', 305 | 'Get a string describing the format of this file') 306 | getReader = jutil.make_method('getReader', 307 | '()Lloci/formats/IFormatReader;') 308 | def allowOpenToCheckType(self, allow): 309 | '''Allow the "isThisType" function to open files 310 | 311 | For the cluster, you want to tell potential file formats 312 | not to open the image file to test if it's their format. 313 | ''' 314 | if not hasattr(self, "allowOpenToCheckType_method"): 315 | self.allowOpenToCheckType_method = None 316 | class_wrapper = jutil.get_class_wrapper(self.o) 317 | methods = class_wrapper.getMethods() 318 | for method in jutil.get_env().get_object_array_elements(methods): 319 | m = jutil.get_method_wrapper(method) 320 | if m.getName() in ('allowOpenToCheckType', 'setAllowOpenFiles'): 321 | self.allowOpenToCheckType_method = m 322 | if self.allowOpenToCheckType_method is not None: 323 | object_class = env.find_class('java/lang/Object') 324 | jexception = jutil.get_env().exception_occurred() 325 | if jexception is not None: 326 | raise jutil.JavaException(jexception) 327 | 328 | boolean_value = jutil.make_instance('java/lang/Boolean', 329 | '(Z)V', allow) 330 | args = jutil.get_env().make_object_array(1, object_class) 331 | jexception = jutil.get_env().exception_occurred() 332 | if jexception is not None: 333 | raise jutil.JavaException(jexception) 334 | jutil.get_env().set_object_array_element(args, 0, boolean_value) 335 | jexception = jutil.get_env().exception_occurred() 336 | if jexception is not None: 337 | raise jutil.JavaException(jexception) 338 | self.allowOpenToCheckType_method.invoke(self.o, args) 339 | return ImageReader 340 | 341 | 342 | def make_reader_wrapper_class(class_name): 343 | '''Make an ImageReader wrapper class 344 | 345 | class_name - the name of the wrapper class, for instance, 346 | "loci/formats/ChannelSeparator" 347 | 348 | You can instantiate an instance of the wrapper class like this: 349 | rdr = ChannelSeparator(ImageReader()) 350 | ''' 351 | IFormatReader = make_iformat_reader_class() 352 | class ReaderWrapper(IFormatReader): 353 | __doc__ = '''A wrapper for %s 354 | 355 | See http://hudson.openmicroscopy.org.uk/job/LOCI/javadoc/loci/formats/ImageReader.html 356 | '''%class_name 357 | new_fn = jutil.make_new(class_name, '(Lloci/formats/IFormatReader;)V') 358 | def __init__(self, rdr): 359 | self.new_fn(rdr) 360 | 361 | setId = jutil.make_method('setId', '(Ljava/lang/String;)V', 362 | 'Set the name of the data file') 363 | return ReaderWrapper 364 | 365 | __has_omero_jars = None 366 | def has_omero_packages(): 367 | '''Return True if we can find the packages needed for OMERO 368 | 369 | In order to run OMERO, you'll need the OMERO client and ICE 370 | on your class path (not supplied with python-bioformats and 371 | specific to your server's version) 372 | ''' 373 | global __has_omero_jars 374 | if __has_omero_jars is None: 375 | class_loader = jutil.static_call( 376 | "java/lang/ClassLoader", "getSystemClassLoader", 377 | "()Ljava/lang/ClassLoader;") 378 | for klass in ("Glacier2.PermissionDeniedException", 379 | "loci.ome.io.OmeroReader", "omero.client"): 380 | try: 381 | jutil.call( 382 | class_loader, "loadClass", 383 | "(Ljava/lang/String;)Ljava/lang/Class;", klass) 384 | except: 385 | __has_omero_jars = False 386 | break 387 | else: 388 | __has_omero_jars = True 389 | return __has_omero_jars 390 | 391 | __omero_server = None 392 | __omero_username = None 393 | __omero_session_id = None 394 | __omero_port = None 395 | __omero_config_file = None 396 | # 397 | # Only set if user enters password in plaintext on command-line 398 | # 399 | __omero_password = None 400 | 401 | def set_omero_credentials(omero_server, omero_port, omero_username, omero_password): 402 | '''Set the credentials to be used to connect to the Omero server 403 | 404 | :param omero_server: DNS name of the server 405 | 406 | :param omero_port: use this port to connect to the server 407 | 408 | :param omero_username: log on as this user 409 | 410 | :param omero_password: log on using this password 411 | 412 | The session ID is valid after this function is called. An exception is thrown 413 | if the login fails. :func:`bioformats.omero_logout()` can be called to log out. 414 | 415 | ''' 416 | global __omero_server 417 | global __omero_username 418 | global __omero_session_id 419 | global __omero_port 420 | __omero_server = omero_server 421 | __omero_port = omero_port 422 | __omero_username = omero_username 423 | script = """ 424 | var client = Packages.omero.client(server, port); 425 | var serverFactory = client.createSession(user, password); 426 | client.getSessionId(); 427 | """ 428 | __omero_session_id = jutil.run_script(script, dict( 429 | server = __omero_server, 430 | port = __omero_port, 431 | user = __omero_username, 432 | password = omero_password)) 433 | return __omero_session_id 434 | 435 | def get_omero_credentials(): 436 | '''Return a pickleable dictionary representing the Omero credentials. 437 | 438 | Call :func:`bioformats.use_omero_credentials` in some other process to use this. 439 | 440 | ''' 441 | if __omero_session_id is None: 442 | omero_login() 443 | 444 | return dict(omero_server = __omero_server, 445 | omero_port = __omero_port, 446 | omero_user = __omero_username, 447 | omero_session_id = __omero_session_id) 448 | 449 | def omero_login(): 450 | global __omero_config_file 451 | global __omero_session_id 452 | global __omero_server 453 | global __omero_username 454 | global __omero_port 455 | global __omero_password 456 | if __omero_config_file is not None and os.path.isfile(__omero_config_file): 457 | env = jutil.get_env() 458 | config = env.make_object_array(1, env.find_class("java/lang/String")) 459 | env.set_object_array_element( 460 | config, 0, env.new_string("--Ice.Config=%s" % __omero_config_file)) 461 | script = """ 462 | var client = Packages.omero.client(config); 463 | client.createSession(); 464 | client.getSessionId(); 465 | """ 466 | __omero_session_id = jutil.run_script(script, dict(config=config)) 467 | elif all([x is not None for x in 468 | (__omero_server, __omero_port, __omero_username, __omero_password)]): 469 | set_omero_credentials(__omero_server, __omero_port, __omero_username, 470 | __omero_password) 471 | else: 472 | __omero_login_fn() 473 | return __omero_session_id 474 | 475 | def omero_logout(): 476 | '''Abandon any current Omero session. 477 | 478 | ''' 479 | global __omero_session_id 480 | __omero_session_id = None 481 | 482 | def use_omero_credentials(credentials): 483 | '''Use the session ID from an existing login as credentials. 484 | 485 | :param credentials: credentials from get_omero_credentials. 486 | 487 | ''' 488 | global __omero_server 489 | global __omero_username 490 | global __omero_session_id 491 | global __omero_port 492 | global __omero_config_file 493 | global __omero_password 494 | __omero_server = credentials.get(K_OMERO_SERVER, None) 495 | __omero_port = credentials.get(K_OMERO_PORT, None) 496 | __omero_username = credentials.get(K_OMERO_USER, None) 497 | __omero_session_id = credentials.get(K_OMERO_SESSION_ID, None) 498 | __omero_config_file = credentials.get(K_OMERO_CONFIG_FILE, None) 499 | __omero_password = credentials.get(K_OMERO_PASSWORD, None) 500 | 501 | __omero_login_fn = None 502 | def set_omero_login_hook(fn): 503 | '''Set the function to be called when a login to Omero is needed. 504 | 505 | ''' 506 | global __omero_login_fn 507 | __omero_login_fn = fn 508 | 509 | def get_omero_reader(): 510 | '''Return an ``loci.ome.io.OMEROReader`` instance, wrapped as a FormatReader. 511 | 512 | ''' 513 | script = """ 514 | var rdr = new Packages.loci.ome.io.OmeroReader(); 515 | rdr.setServer(server); 516 | rdr.setPort(port); 517 | rdr.setUsername(username); 518 | rdr.setSessionID(sessionID); 519 | rdr; 520 | """ 521 | if __omero_session_id is None: 522 | omero_login() 523 | 524 | jrdr = jutil.run_script(script, dict( 525 | server = __omero_server, 526 | port = __omero_port, 527 | username = __omero_username, 528 | sessionID = __omero_session_id)) 529 | 530 | rdr = make_iformat_reader_class()() 531 | rdr.o = jrdr 532 | return rdr 533 | 534 | 535 | def load_using_bioformats_url(url, c=None, z=0, t=0, series=None, index=None, 536 | rescale = True, 537 | wants_max_intensity = False, 538 | channel_names = None): 539 | '''Load a file from Bio-formats via a URL 540 | 541 | ''' 542 | with ImageReader(url=url) as rdr: 543 | return rdr.read(c, z, t, series, index, rescale, wants_max_intensity, 544 | channel_names) 545 | 546 | 547 | class ImageReader(object): 548 | '''Find the appropriate reader for a file. 549 | 550 | This class is meant to be harnessed to a scope like this: 551 | 552 | >>> with ImageReader(path) as reader: 553 | >>> .... 554 | 555 | It uses `__enter__` and `__exit__` to manage the random access stream 556 | that can be used to cache the file contents in memory. 557 | 558 | ''' 559 | 560 | def __init__(self, path=None, url=None, perform_init=True): 561 | self.stream = None 562 | file_scheme = "file:" 563 | self.using_temp_file = False 564 | 565 | if url is not None: 566 | url = str(url) 567 | if url.lower().startswith(file_scheme): 568 | url = url2pathname(url[len(file_scheme):]) 569 | path = url 570 | 571 | self.path = path 572 | if path is None: 573 | if url.lower().startswith("omero:"): 574 | while True: 575 | # 576 | # We keep trying to contact the OMERO server via the 577 | # login dialog until the user gives up or we connect. 578 | # 579 | try: 580 | self.rdr = get_omero_reader() 581 | self.path = url 582 | if perform_init: 583 | self.init_reader() 584 | return 585 | except jutil.JavaException as e: 586 | je = e.throwable 587 | if jutil.is_instance_of( 588 | je, "loci/formats/FormatException"): 589 | je = jutil.call(je, "getCause", 590 | "()Ljava/lang/Throwable;") 591 | if jutil.is_instance_of( 592 | je, "Glacier2/PermissionDeniedException"): 593 | omero_logout() 594 | omero_login() 595 | else: 596 | logger.warn(e) 597 | for line in traceback.format_exc().split("\n"): 598 | logger.warn(line) 599 | if jutil.is_instance_of( 600 | je, "java/io/FileNotFoundException"): 601 | raise IOError( 602 | errno.ENOENT, 603 | "The file, \"%s\", does not exist." % path, 604 | path) 605 | e2 = IOError( 606 | errno.EINVAL, "Could not load the file as an image (see log for details)", path.encode('utf-8')) 607 | raise e2 608 | else: 609 | # 610 | # Other URLS, copy them to a tempfile location 611 | # 612 | filename = self.download(url) 613 | else: 614 | if sys.platform.startswith("win"): 615 | self.path = self.path.replace("/", os.path.sep) 616 | filename = os.path.split(path)[1] 617 | 618 | if not os.path.isfile(self.path): 619 | raise IOError( 620 | errno.ENOENT, 621 | "The file, \"%s\", does not exist." % path, 622 | path) 623 | 624 | self.stream = jutil.make_instance('loci/common/RandomAccessInputStream', 625 | '(Ljava/lang/String;)V', 626 | self.path) 627 | 628 | self.rdr = None 629 | class_list = get_class_list() 630 | find_rdr_script = """ 631 | var classes = class_list.getClasses(); 632 | var rdr = null; 633 | var lc_filename = java.lang.String(filename.toLowerCase()); 634 | for (pass=0; pass < 3; pass++) { 635 | for (class_idx in classes) { 636 | var maybe_rdr = classes[class_idx].newInstance(); 637 | if (pass == 0) { 638 | if (maybe_rdr.isThisType(filename, false)) { 639 | rdr = maybe_rdr; 640 | break; 641 | } 642 | continue; 643 | } else if (pass == 1) { 644 | var suffixes = maybe_rdr.getSuffixes(); 645 | var suffix_found = false; 646 | for (suffix_idx in suffixes) { 647 | var suffix = java.lang.String(suffixes[suffix_idx]); 648 | suffix = suffix.toLowerCase(); 649 | if (lc_filename.endsWith(suffix)) { 650 | suffix_found = true; 651 | break; 652 | } 653 | } 654 | if (! suffix_found) continue; 655 | } 656 | if (maybe_rdr.isThisType(stream)) { 657 | rdr = maybe_rdr; 658 | break; 659 | } 660 | } 661 | if (rdr) break; 662 | } 663 | rdr; 664 | """ 665 | IFormatReader = make_iformat_reader_class() 666 | jrdr = jutil.run_script(find_rdr_script, dict(class_list = class_list, 667 | filename = filename, 668 | stream = self.stream)) 669 | if jrdr is None: 670 | raise ValueError("Could not find a Bio-Formats reader for %s", self.path) 671 | self.rdr = IFormatReader() 672 | self.rdr.o = jrdr 673 | if perform_init: 674 | self.init_reader() 675 | 676 | def download(self, url): 677 | scheme = urlparse(url)[0] 678 | ext = url[url.rfind("."):] 679 | urlpath = urlparse(url)[2] 680 | filename = unquote(urlpath.split("/")[-1]) 681 | 682 | self.using_temp_file = True 683 | 684 | if scheme == 's3': 685 | client = boto3.client('s3') 686 | bucket_name, key = re.compile('s3://([\w\d\-\.]+)/(.*)').search(url).groups() 687 | url = client.generate_presigned_url( 688 | 'get_object', 689 | Params={'Bucket': bucket_name, 'Key': key.replace("+", " ")} 690 | ) 691 | 692 | src = urlopen(url) 693 | dest_fd, self.path = tempfile.mkstemp(suffix=ext) 694 | try: 695 | with os.fdopen(dest_fd, 'wb') as dest: 696 | shutil.copyfileobj(src, dest) 697 | except: 698 | os.remove(self.path) 699 | finally: 700 | src.close() 701 | 702 | return filename 703 | 704 | def __enter__(self): 705 | return self 706 | 707 | def __exit__(self, type_class, value, traceback): 708 | self.close() 709 | 710 | def close(self): 711 | if hasattr(self, "rdr"): 712 | self.rdr.close() 713 | del self.rdr.o 714 | del self.rdr 715 | if hasattr(self, "stream") and self.stream is not None: 716 | jutil.call(self.stream, 'close', '()V') 717 | del self.stream 718 | if self.using_temp_file: 719 | os.remove(self.path) 720 | self.using_temp_file = False 721 | # 722 | # Run the Java garbage collector here. 723 | # 724 | jutil.static_call("java/lang/System", "gc","()V") 725 | 726 | def init_reader(self): 727 | mdoptions = metadatatools.get_metadata_options(metadatatools.ALL) 728 | self.rdr.setMetadataOptions(mdoptions) 729 | self.rdr.setGroupFiles(False) 730 | self.metadata = metadatatools.createOMEXMLMetadata() 731 | self.rdr.setMetadataStore(self.metadata) 732 | try: 733 | self.rdr.setId(self.path) 734 | except jutil.JavaException as e: 735 | logger.warn(e) 736 | for line in traceback.format_exc().split("\n"): 737 | logger.warn(line) 738 | je = e.throwable 739 | if has_omero_packages() and jutil.is_instance_of( 740 | je, "Glacier2/PermissionDeniedException"): 741 | # Handle at a higher level 742 | raise 743 | if jutil.is_instance_of( 744 | je, "loci/formats/FormatException"): 745 | je = jutil.call(je, "getCause", 746 | "()Ljava/lang/Throwable;") 747 | if jutil.is_instance_of( 748 | je, "java/io/FileNotFoundException"): 749 | raise IOError( 750 | errno.ENOENT, 751 | "The file, \"%s\", does not exist." % self.path, 752 | self.path) 753 | e2 = IOError( 754 | errno.EINVAL, "Could not load the file as an image (see log for details)", 755 | self.path.encode('utf-8')) 756 | raise e2 757 | 758 | 759 | def read(self, c = None, z = 0, t = 0, series = None, index = None, 760 | rescale = True, wants_max_intensity = False, channel_names = None, XYWH=None): 761 | '''Read a single plane from the image reader file. 762 | :param c: read from this channel. `None` = read color image if multichannel 763 | or interleaved RGB. 764 | :param z: z-stack index 765 | :param t: time index 766 | :param series: series for ``.flex`` and similar multi-stack formats 767 | :param index: if `None`, fall back to ``zct``, otherwise load the indexed frame 768 | :param rescale: `True` to rescale the intensity scale to 0 and 1; `False` to 769 | return the raw values native to the file. 770 | :param wants_max_intensity: if `False`, only return the image; if `True`, 771 | return a tuple of image and max intensity 772 | :param channel_names: provide the channel names for the OME metadata 773 | :param XYWH: a (x, y, w, h) tuple 774 | ''' 775 | FormatTools = make_format_tools_class() 776 | ChannelSeparator = make_reader_wrapper_class( 777 | "loci/formats/ChannelSeparator") 778 | env = jutil.get_env() 779 | if series is not None: 780 | self.rdr.setSeries(series) 781 | 782 | if XYWH is not None: 783 | assert isinstance(XYWH, tuple) and len(XYWH) == 4, "Invalid XYWH tuple" 784 | openBytes_func = lambda x: self.rdr.openBytesXYWH(x, XYWH[0], XYWH[1], XYWH[2], XYWH[3]) 785 | width, height = XYWH[2], XYWH[3] 786 | else: 787 | openBytes_func = self.rdr.openBytes 788 | width, height = self.rdr.getSizeX(), self.rdr.getSizeY() 789 | 790 | pixel_type = self.rdr.getPixelType() 791 | little_endian = self.rdr.isLittleEndian() 792 | if pixel_type == FormatTools.INT8: 793 | dtype = np.int8 794 | scale = 255 795 | elif pixel_type == FormatTools.UINT8: 796 | dtype = np.uint8 797 | scale = 255 798 | elif pixel_type == FormatTools.UINT16: 799 | dtype = 'u2' 800 | scale = 65535 801 | elif pixel_type == FormatTools.INT16: 802 | dtype = 'i2' 803 | scale = 65535 804 | elif pixel_type == FormatTools.UINT32: 805 | dtype = 'u4' 806 | scale = 2**32 807 | elif pixel_type == FormatTools.INT32: 808 | dtype = 'i4' 809 | scale = 2**32-1 810 | elif pixel_type == FormatTools.FLOAT: 811 | dtype = 'f4' 812 | scale = 1 813 | elif pixel_type == FormatTools.DOUBLE: 814 | dtype = 'f8' 815 | scale = 1 816 | max_sample_value = self.rdr.getMetadataValue('MaxSampleValue') 817 | if max_sample_value is not None: 818 | try: 819 | scale = jutil.call(max_sample_value, 'intValue', '()I') 820 | except: 821 | logger.warning("WARNING: failed to get MaxSampleValue for image. Intensities may be improperly scaled.") 822 | if index is not None: 823 | image = np.frombuffer(openBytes_func(index), dtype) 824 | if len(image) / height / width in (3,4): 825 | n_channels = int(len(image) / height / width) 826 | if self.rdr.isInterleaved(): 827 | image.shape = (height, width, n_channels) 828 | else: 829 | image.shape = (n_channels, height, width) 830 | image = image.transpose(1, 2, 0) 831 | else: 832 | image.shape = (height, width) 833 | elif self.rdr.isRGB() and self.rdr.isInterleaved(): 834 | index = self.rdr.getIndex(z,0,t) 835 | image = np.frombuffer(openBytes_func(index), dtype) 836 | image.shape = (height, width, self.rdr.getSizeC()) 837 | if image.shape[2] > 3: 838 | image = image[:, :, :3] 839 | elif c is not None and self.rdr.getRGBChannelCount() == 1: 840 | index = self.rdr.getIndex(z,c,t) 841 | image = np.frombuffer(openBytes_func(index), dtype) 842 | image.shape = (height, width) 843 | elif self.rdr.getRGBChannelCount() > 1: 844 | n_planes = self.rdr.getRGBChannelCount() 845 | rdr = ChannelSeparator(self.rdr) 846 | planes = [ 847 | np.frombuffer( 848 | (rdr.openBytes(rdr.getIndex(z,i,t)) if XYWH is None else 849 | rdr.openBytesXYWH(rdr.getIndex(z,i,t), XYWH[0], XYWH[1], XYWH[2], XYWH[3])), 850 | dtype 851 | ) for i in range(n_planes)] 852 | 853 | if len(planes) > 3: 854 | planes = planes[:3] 855 | elif len(planes) < 3: 856 | # > 1 and < 3 means must be 2 857 | # see issue #775 858 | planes.append(np.zeros(planes[0].shape, planes[0].dtype)) 859 | image = np.dstack(planes) 860 | image.shape=(height, width, 3) 861 | del rdr 862 | elif self.rdr.getSizeC() > 1: 863 | images = [ 864 | np.frombuffer(openBytes_func(self.rdr.getIndex(z,i,t)), dtype) 865 | for i in range(self.rdr.getSizeC())] 866 | image = np.dstack(images) 867 | image.shape = (height, width, self.rdr.getSizeC()) 868 | if not channel_names is None: 869 | metadata = metadatatools.MetadataRetrieve(self.metadata) 870 | for i in range(self.rdr.getSizeC()): 871 | index = self.rdr.getIndex(z, 0, t) 872 | channel_name = metadata.getChannelName(index, i) 873 | if channel_name is None: 874 | channel_name = metadata.getChannelID(index, i) 875 | channel_names.append(channel_name) 876 | elif self.rdr.isIndexed(): 877 | # 878 | # The image data is indexes into a color lookup-table 879 | # But sometimes the table is the identity table and just generates 880 | # a monochrome RGB image 881 | # 882 | index = self.rdr.getIndex(z,0,t) 883 | image = np.frombuffer(openBytes_func(index),dtype) 884 | if pixel_type in (FormatTools.INT16, FormatTools.UINT16): 885 | lut = self.rdr.get16BitLookupTable() 886 | if lut is not None: 887 | lut = np.array( 888 | [env.get_short_array_elements(d) 889 | for d in env.get_object_array_elements(lut)])\ 890 | .transpose() 891 | else: 892 | lut = self.rdr.get8BitLookupTable() 893 | if lut is not None: 894 | lut = np.array( 895 | [env.get_byte_array_elements(d) 896 | for d in env.get_object_array_elements(lut)])\ 897 | .transpose() 898 | image.shape = (height, width) 899 | if (lut is not None) \ 900 | and not np.all(lut == np.arange(lut.shape[0])[:, np.newaxis]): 901 | image = lut[image, :] 902 | else: 903 | index = self.rdr.getIndex(z,0,t) 904 | image = np.frombuffer(openBytes_func(index),dtype) 905 | image.shape = (height,width) 906 | 907 | if rescale: 908 | image = image.astype(np.float32) / float(scale) 909 | if wants_max_intensity: 910 | return image, scale 911 | return image 912 | 913 | ################### 914 | # 915 | # A cache mechanism for image readers 916 | # 917 | # CellProfiler's analysis worker will read image planes from the same 918 | # file across different jobs, so only a global cache of image readers 919 | # will work. Here, we try and keep around one reader per key - the key 920 | # typically being its image name in CellProfiler. We also need to clear 921 | # the cache globally. 922 | # 923 | #################### 924 | 925 | # The key cache associates key with path/url 926 | # This allows us to have two keys point to the same reader, e.g. read 927 | # multiple channels from a stack. 928 | __image_reader_key_cache = {} 929 | # The image reader cache associates path/url with a reader 930 | __image_reader_cache = {} 931 | 932 | def get_image_reader(key, path=None, url=None): 933 | '''Make or find an image reader appropriate for the given path 934 | 935 | path - pathname to the reader on disk. 936 | 937 | key - use this key to keep only a single cache member associated with 938 | that key open at a time. 939 | ''' 940 | logger.debug("Getting image reader for: %s, %s, %s" % (key, path, url)) 941 | if key in __image_reader_key_cache: 942 | old_path, old_url = __image_reader_key_cache[key] 943 | old_count, rdr = __image_reader_cache[old_path, old_url] 944 | if old_path == path and old_url == url: 945 | return rdr 946 | release_image_reader(key) 947 | if (path, url) in __image_reader_cache: 948 | old_count, rdr = __image_reader_cache[path, url] 949 | else: 950 | # If OMERO.py is found on the PYTHONPATH and OMERO_READER_ENABLED 951 | # is True OMERO python reader can be used to directly request 952 | # the image pixels from the server. 953 | # Following this route gives almost 10x speed up. 954 | if OMERO_READER_IMPORTED and OMERO_IMPORTED and \ 955 | omero_reader_enabled() and \ 956 | url is not None and url.lower().startswith("omero:"): 957 | logger.debug("Initializing Python reader.") 958 | rdr = OmeroReader(__omero_server, __omero_session_id, url=url) 959 | else: 960 | logger.debug("Falling back to Java reader.") 961 | rdr = ImageReader(path=path, url=url) 962 | old_count = 0 963 | __image_reader_cache[path, url] = (old_count+1, rdr) 964 | __image_reader_key_cache[key] = (path, url) 965 | return rdr 966 | 967 | def release_image_reader(key): 968 | '''Tell the cache that it should flush the reference for the given key 969 | 970 | ''' 971 | if key in __image_reader_key_cache: 972 | path, url = __image_reader_key_cache[key] 973 | del __image_reader_key_cache[key] 974 | old_count, rdr = __image_reader_cache[path, url] 975 | if old_count == 1: 976 | rdr.close() 977 | del __image_reader_cache[path, url] 978 | else: 979 | __image_reader_cache[path, url] = (old_count-1, rdr) 980 | 981 | def clear_image_reader_cache(): 982 | '''Get rid of any open image readers''' 983 | for use_count, rdr in __image_reader_cache.values(): 984 | logger.debug("Closing reader %s" % rdr) 985 | rdr.close() 986 | __image_reader_cache.clear() 987 | __image_reader_key_cache.clear() 988 | 989 | def load_using_bioformats(path, c=None, z=0, t=0, series=None, index=None, 990 | rescale = True, 991 | wants_max_intensity = False, 992 | channel_names = None): 993 | '''Load the given image file using the Bioformats library. 994 | 995 | :param path: path to the file 996 | :param z: the frame index in the `z` (depth) dimension. 997 | :param t: the frame index in the time dimension. 998 | :param channel_names: `None` if you don't want them, a list which will be filled if you do. 999 | 1000 | :returns: either a 2-d (grayscale) or 3-d (2-d + 3 RGB planes) image. 1001 | 1002 | ''' 1003 | 1004 | with ImageReader(path=path) as rdr: 1005 | return rdr.read(c, z, t, series, index, rescale, wants_max_intensity, 1006 | channel_names) 1007 | 1008 | def get_omexml_metadata(path=None, url=None): 1009 | '''Read the OME metadata from a file using Bio-formats 1010 | 1011 | :param path: path to the file 1012 | 1013 | :param groupfiles: utilize the groupfiles option to take the directory structure 1014 | into account. 1015 | 1016 | :returns: the metdata as XML. 1017 | 1018 | ''' 1019 | with ImageReader(path=path, url=url, perform_init=False) as rdr: 1020 | # 1021 | # Below, "in" is a keyword and Rhino's parser is just a little wonky I fear. 1022 | # 1023 | # It is critical that setGroupFiles be set to false, goodness knows 1024 | # why, but if you don't the series count is wrong for flex files. 1025 | # 1026 | script = """ 1027 | importClass(Packages.loci.common.services.ServiceFactory, 1028 | Packages.loci.formats.services.OMEXMLService, 1029 | Packages.loci.formats['in'].DefaultMetadataOptions, 1030 | Packages.loci.formats['in'].MetadataLevel); 1031 | reader.setGroupFiles(false); 1032 | reader.setOriginalMetadataPopulated(true); 1033 | var service = new ServiceFactory().getInstance(OMEXMLService); 1034 | var metadata = service.createOMEXMLMetadata(); 1035 | reader.setMetadataStore(metadata); 1036 | reader.setMetadataOptions(new DefaultMetadataOptions(MetadataLevel.ALL)); 1037 | reader.setId(path); 1038 | var xml = service.getOMEXML(metadata); 1039 | xml; 1040 | """ 1041 | xml = jutil.run_script(script, dict(path=rdr.path, reader = rdr.rdr)) 1042 | return xml 1043 | -------------------------------------------------------------------------------- /bioformats/formatwriter.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | '''formatwriter.py - mechanism to wrap a bioformats WriterWrapper and ImageWriter 9 | 10 | The following file formats can be written using Bio-Formats: 11 | 12 | - TIFF (uncompressed or LZW) 13 | - OME-TIFF (uncompressed or LZW) 14 | - JPEG 15 | - PNG 16 | - AVI (uncompressed) 17 | - QuickTime (uncompressed is supported natively; additional codecs use QTJava) 18 | - Encapsulated PostScript (EPS) 19 | 20 | Support for OME-XML in the near future. 21 | 22 | The writer API (see loci.formats.IFormatWriter) is very similar to the reader 23 | API, in that files are written one plane at time (rather than all at once). 24 | 25 | All writers allow the output file to be changed before the last plane has 26 | been written. This allows you to write to any number of output files using 27 | the same writer and output settings (compression, frames per second, etc.), 28 | and is especially useful for formats that do not support multiple images per 29 | file. 30 | 31 | ''' 32 | 33 | from __future__ import absolute_import, print_function, unicode_literals 34 | 35 | __version__ = "$Revision$" 36 | 37 | import numpy as np 38 | import os 39 | import sys 40 | 41 | import javabridge as jutil 42 | import bioformats 43 | import javabridge as javabridge 44 | import bioformats.omexml as ome 45 | 46 | def write_image(pathname, pixels, pixel_type, 47 | c = 0, z = 0, t = 0, 48 | size_c = 1, size_z = 1, size_t = 1, 49 | channel_names = None): 50 | """Write the image using bioformats. 51 | 52 | :param filename: save to this filename 53 | 54 | :param pixels: the image to save 55 | 56 | :param pixel_type: save using this pixel type 57 | 58 | :param c: the image's channel index 59 | 60 | :param z: the image's `z` index 61 | 62 | :param t: the image's `t` index 63 | 64 | :param size_c: # of channels in the stack 65 | 66 | :param size_z: # of z stacks 67 | 68 | :param size_t: # of timepoints in the stack 69 | 70 | :param channel_names: names of the channels (make up names if not present). 71 | 72 | """ 73 | omexml = ome.OMEXML() 74 | omexml.image(0).Name = os.path.split(pathname)[1] 75 | p = omexml.image(0).Pixels 76 | assert isinstance(p, ome.OMEXML.Pixels) 77 | p.SizeX = pixels.shape[1] 78 | p.SizeY = pixels.shape[0] 79 | p.SizeC = size_c 80 | p.SizeT = size_t 81 | p.SizeZ = size_z 82 | p.DimensionOrder = ome.DO_XYCZT 83 | p.PixelType = pixel_type 84 | index = c + size_c * z + size_c * size_z * t 85 | if pixels.ndim == 3: 86 | p.SizeC = pixels.shape[2] 87 | p.Channel(0).SamplesPerPixel = pixels.shape[2] 88 | omexml.structured_annotations.add_original_metadata( 89 | ome.OM_SAMPLES_PER_PIXEL, str(pixels.shape[2])) 90 | elif size_c > 1: 91 | p.channel_count = size_c 92 | 93 | pixel_buffer = convert_pixels_to_buffer(pixels, pixel_type) 94 | xml = omexml.to_xml() 95 | script = """ 96 | importClass(Packages.loci.formats.services.OMEXMLService, 97 | Packages.loci.common.services.ServiceFactory, 98 | Packages.loci.formats.ImageWriter); 99 | var service = new ServiceFactory().getInstance(OMEXMLService); 100 | var metadata = service.createOMEXMLMetadata(xml); 101 | var writer = new ImageWriter(); 102 | writer.setMetadataRetrieve(metadata); 103 | writer.setId(path); 104 | writer.setInterleaved(true); 105 | writer.saveBytes(index, buffer); 106 | writer.close(); 107 | """ 108 | jutil.run_script(script, 109 | dict(path=pathname, 110 | xml=xml, 111 | index=index, 112 | buffer=pixel_buffer)) 113 | 114 | def convert_pixels_to_buffer(pixels, pixel_type): 115 | '''Convert the pixels in the image into a buffer of the right pixel type 116 | 117 | pixels - a 2d monochrome or color image 118 | 119 | pixel_type - one of the OME pixel types 120 | 121 | returns a 1-d byte array 122 | ''' 123 | if pixel_type in (ome.PT_UINT8, ome.PT_INT8, ome.PT_BIT): 124 | as_dtype = np.uint8 125 | elif pixel_type in (ome.PT_UINT16, ome.PT_INT16): 126 | as_dtype = "/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-javabridge.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-javabridge.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/python-javabridge" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-javabridge" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # python-bioformats documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Aug 1 12:45:16 2013. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | from __future__ import absolute_import, unicode_literals 15 | 16 | import sys, os 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | sys.path.insert(0, os.path.abspath('..')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # If your documentation needs a minimal Sphinx version, state it here. 26 | #needs_sphinx = '1.0' 27 | 28 | # Add any Sphinx extension module names here, as strings. They can be extensions 29 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 30 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 31 | 32 | # Add any paths that contain templates here, relative to this directory. 33 | templates_path = ['_templates'] 34 | 35 | # The suffix of source filenames. 36 | source_suffix = '.rst' 37 | 38 | # The encoding of source files. 39 | #source_encoding = 'utf-8-sig' 40 | 41 | # The master toctree document. 42 | master_doc = 'index' 43 | 44 | # General information about the project. 45 | project = 'python-bioformats' 46 | copyright = '2014, Broad Institute of MIT and Harvard' 47 | 48 | def get_version(): 49 | version_file = os.path.join(os.path.dirname(__file__), '..', 'bioformats', 50 | '_version.py') 51 | if os.path.exists(version_file): 52 | with open(version_file) as f: 53 | cached_version_line = f.read().strip() 54 | try: 55 | import re 56 | # From http://stackoverflow.com/a/3619714/17498 57 | cached_version = re.search(r"^version = ['\"]([^'\"]*)['\"]", 58 | cached_version_line, re.M).group(1) 59 | except: 60 | raise RuntimeError("Unable to find version in %s" % version_file) 61 | split_version = cached_version.split('.') 62 | return '.'.join(split_version[:2]), cached_version 63 | else: 64 | return '0.0', '0.0.0' 65 | 66 | # The version info for the project you're documenting, acts as replacement for 67 | # |version| and |release|, also used in various other places throughout the 68 | # built documents. 69 | # 70 | # version: The short X.Y version. 71 | # release: The full version, including alpha/beta/rc tags. 72 | version, release = get_version() 73 | 74 | # The language for content autogenerated by Sphinx. Refer to documentation 75 | # for a list of supported languages. 76 | #language = None 77 | 78 | # There are two options for replacing |today|: either, you set today to some 79 | # non-false value, then it is used: 80 | #today = '' 81 | # Else, today_fmt is used as the format for a strftime call. 82 | #today_fmt = '%B %d, %Y' 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | exclude_patterns = ['_build'] 87 | 88 | # The reST default role (used for this markup: `text`) to use for all documents. 89 | #default_role = None 90 | 91 | # If true, '()' will be appended to :func: etc. cross-reference text. 92 | #add_function_parentheses = True 93 | 94 | # If true, the current module name will be prepended to all description 95 | # unit titles (such as .. function::). 96 | #add_module_names = True 97 | 98 | # If true, sectionauthor and moduleauthor directives will be shown in the 99 | # output. They are ignored by default. 100 | #show_authors = False 101 | 102 | # The name of the Pygments (syntax highlighting) style to use. 103 | pygments_style = 'sphinx' 104 | 105 | # A list of ignored prefixes for module index sorting. 106 | #modindex_common_prefix = [] 107 | 108 | # If true, keep warnings as "system message" paragraphs in the built documents. 109 | #keep_warnings = False 110 | 111 | 112 | # -- Options for HTML output --------------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | html_theme = 'default' 117 | 118 | # Theme options are theme-specific and customize the look and feel of a theme 119 | # further. For a list of options available for each theme, see the 120 | # documentation. 121 | #html_theme_options = {} 122 | 123 | # Add any paths that contain custom themes here, relative to this directory. 124 | #html_theme_path = [] 125 | 126 | # The name for this set of Sphinx documents. If None, it defaults to 127 | # " v documentation". 128 | #html_title = None 129 | 130 | # A shorter title for the navigation bar. Default is the same as html_title. 131 | #html_short_title = None 132 | 133 | # The name of an image file (relative to this directory) to place at the top 134 | # of the sidebar. 135 | #html_logo = None 136 | 137 | # The name of an image file (within the static path) to use as favicon of the 138 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 139 | # pixels large. 140 | #html_favicon = None 141 | 142 | # Add any paths that contain custom static files (such as style sheets) here, 143 | # relative to this directory. They are copied after the builtin static files, 144 | # so a file named "default.css" will overwrite the builtin "default.css". 145 | html_static_path = ['_static'] 146 | 147 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 148 | # using the given strftime format. 149 | #html_last_updated_fmt = '%b %d, %Y' 150 | 151 | # If true, SmartyPants will be used to convert quotes and dashes to 152 | # typographically correct entities. 153 | #html_use_smartypants = True 154 | 155 | # Custom sidebar templates, maps document names to template names. 156 | #html_sidebars = {} 157 | 158 | # Additional templates that should be rendered to pages, maps page names to 159 | # template names. 160 | #html_additional_pages = {} 161 | 162 | # If false, no module index is generated. 163 | #html_domain_indices = True 164 | 165 | # If false, no index is generated. 166 | #html_use_index = True 167 | 168 | # If true, the index is split into individual pages for each letter. 169 | #html_split_index = False 170 | 171 | # If true, links to the reST sources are added to the pages. 172 | #html_show_sourcelink = True 173 | 174 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 175 | #html_show_sphinx = True 176 | 177 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 178 | #html_show_copyright = True 179 | 180 | # If true, an OpenSearch description file will be output, and all pages will 181 | # contain a tag referring to it. The value of this option must be the 182 | # base URL from which the finished HTML is served. 183 | #html_use_opensearch = '' 184 | 185 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 186 | #html_file_suffix = None 187 | 188 | # Output file base name for HTML help builder. 189 | htmlhelp_basename = 'python-bioformatsdoc' 190 | 191 | 192 | # -- Options for LaTeX output -------------------------------------------------- 193 | 194 | latex_elements = { 195 | # The paper size ('letterpaper' or 'a4paper'). 196 | #'papersize': 'letterpaper', 197 | 198 | # The font size ('10pt', '11pt' or '12pt'). 199 | #'pointsize': '10pt', 200 | 201 | # Additional stuff for the LaTeX preamble. 202 | #'preamble': '', 203 | } 204 | 205 | # Grouping the document tree into LaTeX files. List of tuples 206 | # (source start file, target name, title, author, documentclass [howto/manual]). 207 | latex_documents = [ 208 | ('index', 'python-bioformats.tex', 'python-bioformats Documentation', 209 | 'Lee Kamentsky, Vebjorn Ljosa', 'manual'), 210 | ] 211 | 212 | # The name of an image file (relative to this directory) to place at the top of 213 | # the title page. 214 | #latex_logo = None 215 | 216 | # For "manual" documents, if this is true, then toplevel headings are parts, 217 | # not chapters. 218 | #latex_use_parts = False 219 | 220 | # If true, show page references after internal links. 221 | #latex_show_pagerefs = False 222 | 223 | # If true, show URL addresses after external links. 224 | #latex_show_urls = False 225 | 226 | # Documents to append as an appendix to all manuals. 227 | #latex_appendices = [] 228 | 229 | # If false, no module index is generated. 230 | #latex_domain_indices = True 231 | 232 | 233 | # -- Options for manual page output -------------------------------------------- 234 | 235 | # One entry per manual page. List of tuples 236 | # (source start file, name, description, authors, manual section). 237 | man_pages = [ 238 | ('index', 'python-bioformats', 'python-bioformats Documentation', 239 | ['Lee Kamentsky, Vebjorn Ljosa'], 1) 240 | ] 241 | 242 | # If true, show URL addresses after external links. 243 | #man_show_urls = False 244 | 245 | 246 | # -- Options for Texinfo output ------------------------------------------------ 247 | 248 | # Grouping the document tree into Texinfo files. List of tuples 249 | # (source start file, target name, title, author, 250 | # dir menu entry, description, category) 251 | texinfo_documents = [ 252 | ('index', 'python-bioformats', 'python-bioformats Documentation', 253 | 'Lee Kamentsky, Vebjorn Ljosa', 'python-bioformats', 'One line description of project.', 254 | 'Miscellaneous'), 255 | ] 256 | 257 | # Documents to append as an appendix to all manuals. 258 | #texinfo_appendices = [] 259 | 260 | # If false, no module index is generated. 261 | #texinfo_domain_indices = True 262 | 263 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 264 | #texinfo_show_urls = 'footnote' 265 | 266 | # If true, do not generate a @detailmenu in the "Top" node's menu. 267 | #texinfo_no_detailmenu = False 268 | 269 | 270 | # Example configuration for intersphinx: refer to the Python standard library. 271 | intersphinx_mapping = {'javabridge': ('http://pythonhosted.org/javabridge/', None)} 272 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | =================================================================== 2 | python-bioformats: read and write life sciences image file formats 3 | =================================================================== 4 | 5 | The python-bioformats package is an interface to the `Bio-Formats 6 | `_ library 7 | for reading and writing life sciences image file formats. 8 | 9 | Because Bio-Formats is a Java library, python-bioformats uses 10 | python-javabridge to start and interact with a Java virtual machine. 11 | 12 | Python-bioformats and python-javabridge were developed for and are 13 | used by the cell image analysis software CellProfiler 14 | (cellprofiler.org). 15 | 16 | python-bioformats is licensed under the GNU General Public License 17 | (GPL). Many files are licensed under the more permissive BSD license. 18 | 19 | 20 | Installation and testing 21 | ======================== 22 | 23 | Install using pip 24 | ----------------- 25 | 26 | :: 27 | 28 | pip install python-bioformats 29 | 30 | Running the unit tests 31 | ---------------------- 32 | 33 | Running the unit tests requires Nose:: 34 | 35 | nosetests 36 | 37 | On some installations, the following also works:: 38 | 39 | python nosetests.py 40 | 41 | 42 | Starting the JVM 43 | ================ 44 | 45 | When starting the Java virtual machine with python-javabridge's 46 | :py:func:`javabridge.start_vm`, you must add the contents of 47 | :py:data:`bioformats.JARS` to the class path. Example: 48 | 49 | >>> import javabridge 50 | >>> import bioformats 51 | >>> javabridge.start_vm(class_path=bioformats.JARS) 52 | 53 | .. autodata:: bioformats.JARS 54 | :annotation: list of strings 55 | 56 | Initialization and termination 57 | ============================== 58 | 59 | The javabridge package must be used to start the JVM with loci_tools.jar 60 | which is the Bio-Formats library packaged with python-bioformats or with 61 | your own build of Bio-Formats. As a convenience, bioformats.JARS has a list 62 | of the required jar files. 63 | 64 | .. code:: 65 | 66 | import javabridge 67 | import bioformats 68 | javabridge.start_vm(class_path=bioformats.JARS) 69 | 70 | # your program goes here 71 | 72 | javabridge.kill_vm() 73 | 74 | Reading images 75 | ============== 76 | 77 | .. autoclass:: bioformats.ImageReader 78 | 79 | .. automethod:: bioformats.ImageReader.read 80 | .. automethod:: bioformats.ImageReader.close 81 | 82 | Convenience functions that create an image reader for a file path or 83 | URL and use it to read an image: 84 | 85 | .. autofunction:: bioformats.load_image 86 | .. autofunction:: bioformats.load_image_url 87 | 88 | 89 | Cached image readers 90 | ==================== 91 | 92 | .. autofunction:: bioformats.get_image_reader 93 | .. autofunction:: bioformats.release_image_reader 94 | .. autofunction:: bioformats.clear_image_reader_cache 95 | 96 | 97 | Metadata 98 | ======== 99 | 100 | .. autofunction:: bioformats.get_omexml_metadata 101 | .. autoclass:: bioformats.OMEXML 102 | 103 | .. autoattribute:: bioformats.OMEXML.image_count 104 | :annotation: Settable. 105 | .. automethod:: bioformats.OMEXML.image 106 | 107 | .. autoclass:: bioformats::OMEXML.Image 108 | 109 | .. autoattribute:: bioformats::OMEXML.Image.Pixels 110 | 111 | 112 | Writing images 113 | ============== 114 | 115 | .. autofunction:: bioformats.write_image 116 | 117 | .. autodata:: bioformats.PT_UINT16 118 | .. autodata:: bioformats.PT_UINT8 119 | .. autodata:: bioformats.PT_BIT 120 | 121 | 122 | OMERO 123 | ===== 124 | 125 | Python-bioformats can load images from OMERO URLs. To do this, you'll need 126 | to put the JAR files for your OMERO server version onto your Java classpath 127 | when you start. For OMERO server 5.0.0, these are blitz.jar, common.jar, 128 | ice.jar, ice-glacier2.jar, ice-storm.jar and ice-grid.jar. You'll also need 129 | to use the matching version of the Bio-formats library for the OMERO release 130 | instead of the one included with python-bioformats. 131 | 132 | .. autofunction:: bioformats.use_omero_credentials 133 | .. autofunction:: bioformats.set_omero_credentials 134 | .. autofunction:: bioformats.get_omero_credentials 135 | .. autofunction:: bioformats.omero_logout 136 | 137 | .. autofunction:: bioformats.set_omero_login_hook 138 | 139 | Keys for the `credentials` dict 140 | ------------------------------- 141 | 142 | .. autodata:: bioformats.K_OMERO_SERVER 143 | .. autodata:: bioformats.K_OMERO_PORT 144 | .. autodata:: bioformats.K_OMERO_USER 145 | .. autodata:: bioformats.K_OMERO_SESSION_ID 146 | .. autodata:: bioformats.K_OMERO_PASSWORD 147 | .. autodata:: bioformats.K_OMERO_CONFIG_FILE 148 | 149 | 150 | 151 | 152 | Indices and tables 153 | ================== 154 | 155 | * :ref:`genindex` 156 | * :ref:`modindex` 157 | * :ref:`search` 158 | 159 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. xml to make Docutils-native XML files 37 | echo. pseudoxml to make pseudoxml-XML files for display purposes 38 | echo. linkcheck to check all external links for integrity 39 | echo. doctest to run all doctests embedded in the documentation if enabled 40 | goto end 41 | ) 42 | 43 | if "%1" == "clean" ( 44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 45 | del /q /s %BUILDDIR%\* 46 | goto end 47 | ) 48 | 49 | 50 | %SPHINXBUILD% 2> nul 51 | if errorlevel 9009 ( 52 | echo. 53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 54 | echo.installed, then set the SPHINXBUILD environment variable to point 55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 56 | echo.may add the Sphinx directory to PATH. 57 | echo. 58 | echo.If you don't have Sphinx installed, grab it from 59 | echo.http://sphinx-doc.org/ 60 | exit /b 1 61 | ) 62 | 63 | if "%1" == "html" ( 64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 68 | goto end 69 | ) 70 | 71 | if "%1" == "dirhtml" ( 72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 76 | goto end 77 | ) 78 | 79 | if "%1" == "singlehtml" ( 80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 84 | goto end 85 | ) 86 | 87 | if "%1" == "pickle" ( 88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can process the pickle files. 92 | goto end 93 | ) 94 | 95 | if "%1" == "json" ( 96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 97 | if errorlevel 1 exit /b 1 98 | echo. 99 | echo.Build finished; now you can process the JSON files. 100 | goto end 101 | ) 102 | 103 | if "%1" == "htmlhelp" ( 104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 105 | if errorlevel 1 exit /b 1 106 | echo. 107 | echo.Build finished; now you can run HTML Help Workshop with the ^ 108 | .hhp project file in %BUILDDIR%/htmlhelp. 109 | goto end 110 | ) 111 | 112 | if "%1" == "qthelp" ( 113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 114 | if errorlevel 1 exit /b 1 115 | echo. 116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 117 | .qhcp project file in %BUILDDIR%/qthelp, like this: 118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-javabridge.qhcp 119 | echo.To view the help file: 120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-javabridge.ghc 121 | goto end 122 | ) 123 | 124 | if "%1" == "devhelp" ( 125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished. 129 | goto end 130 | ) 131 | 132 | if "%1" == "epub" ( 133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 137 | goto end 138 | ) 139 | 140 | if "%1" == "latex" ( 141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 145 | goto end 146 | ) 147 | 148 | if "%1" == "latexpdf" ( 149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 150 | cd %BUILDDIR%/latex 151 | make all-pdf 152 | cd %BUILDDIR%/.. 153 | echo. 154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 155 | goto end 156 | ) 157 | 158 | if "%1" == "latexpdfja" ( 159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 160 | cd %BUILDDIR%/latex 161 | make all-pdf-ja 162 | cd %BUILDDIR%/.. 163 | echo. 164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex. 165 | goto end 166 | ) 167 | 168 | if "%1" == "text" ( 169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 170 | if errorlevel 1 exit /b 1 171 | echo. 172 | echo.Build finished. The text files are in %BUILDDIR%/text. 173 | goto end 174 | ) 175 | 176 | if "%1" == "man" ( 177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 178 | if errorlevel 1 exit /b 1 179 | echo. 180 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 181 | goto end 182 | ) 183 | 184 | if "%1" == "texinfo" ( 185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 186 | if errorlevel 1 exit /b 1 187 | echo. 188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 189 | goto end 190 | ) 191 | 192 | if "%1" == "gettext" ( 193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 194 | if errorlevel 1 exit /b 1 195 | echo. 196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 197 | goto end 198 | ) 199 | 200 | if "%1" == "changes" ( 201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 202 | if errorlevel 1 exit /b 1 203 | echo. 204 | echo.The overview file is in %BUILDDIR%/changes. 205 | goto end 206 | ) 207 | 208 | if "%1" == "linkcheck" ( 209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 210 | if errorlevel 1 exit /b 1 211 | echo. 212 | echo.Link check complete; look for any errors in the above output ^ 213 | or in %BUILDDIR%/linkcheck/output.txt. 214 | goto end 215 | ) 216 | 217 | if "%1" == "doctest" ( 218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 219 | if errorlevel 1 exit /b 1 220 | echo. 221 | echo.Testing of doctests in the sources finished, look at the ^ 222 | results in %BUILDDIR%/doctest/output.txt. 223 | goto end 224 | ) 225 | 226 | if "%1" == "xml" ( 227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml 228 | if errorlevel 1 exit /b 1 229 | echo. 230 | echo.Build finished. The XML files are in %BUILDDIR%/xml. 231 | goto end 232 | ) 233 | 234 | if "%1" == "pseudoxml" ( 235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml 236 | if errorlevel 1 exit /b 1 237 | echo. 238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. 239 | goto end 240 | ) 241 | 242 | :end 243 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | with-javabridge = True 3 | classpath = bioformats/jars/loci_tools.jar 4 | 5 | [build_sphinx] 6 | source-dir = docs/ 7 | build-dir = docs/_build 8 | all_files = 1 9 | 10 | [upload_sphinx] 11 | upload-dir = docs/_build/html 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | setuptools.setup( 4 | author="Lee Kamentsky", 5 | author_email="leek@broadinstitute.org", 6 | classifiers=[ 7 | "Development Status :: 5 - Production/Stable", 8 | "License :: OSI Approved :: GNU General Public License v2 (GPLv2)", 9 | "Programming Language :: Python :: 2", 10 | "Programming Language :: Python :: 3", 11 | "Programming Language :: Java", 12 | "Topic :: Scientific/Engineering :: Bio-Informatics", 13 | "Topic :: Multimedia :: Graphics :: Graphics Conversion" 14 | ], 15 | description="Read and write life sciences file formats", 16 | extras_require={ 17 | "test": [ 18 | "pytest>=3.3.2,<4" 19 | ] 20 | }, 21 | install_requires=[ 22 | "boto3>=1.14.23", 23 | "future>=0.18.2", 24 | "python-javabridge~=4.0.3" 25 | ], 26 | license="GPL License", 27 | long_description="""Python-bioformats is a Python wrapper for Bio-Formats, a standalone Java library for reading 28 | and writing life sciences image file formats. Bio-Formats is capable of parsing both pixels and metadata for a 29 | large number of formats, as well as writing to several formats. Python-bioformats uses the python-javabridge to 30 | start a Java virtual machine from Python and interact with it. Python-bioformats was developed for and is used by 31 | the cell image analysis software CellProfiler (cellprofiler.org). While we are gratified that others 32 | outside the CellProfiler team find it useful, we maintain python-bioformats essentially for the CellProfiler project 33 | and **cannot currently guarantee support for other users.** Please consider visiting our forum at forum.image.sc for 34 | additional support help.""", 35 | name="python-bioformats", 36 | package_data={ 37 | "bioformats": [ 38 | "jars/*.jar" 39 | ] 40 | }, 41 | packages=[ 42 | "bioformats" 43 | ], 44 | url="http://github.com/CellProfiler/python-bioformats/", 45 | version="4.1.0" 46 | ) 47 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CellProfiler/python-bioformats/e5f75fd72ceeedf7ef8ded2ddaedb347fc70c7e1/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import javabridge 4 | import pytest 5 | 6 | import bioformats 7 | 8 | 9 | @pytest.fixture(autouse=True, scope="session") 10 | def setup_and_teardown(): 11 | log_config = os.path.join(os.path.split(__file__)[0], "resources", "logback.properties") 12 | 13 | javabridge.start_vm( 14 | args=[ 15 | "-Dlogback.configuration=file:{}".format(log_config), 16 | ], 17 | class_path=bioformats.JARS, 18 | run_headless=True 19 | ) 20 | 21 | yield 22 | 23 | javabridge.kill_vm() 24 | -------------------------------------------------------------------------------- /tests/resources/Channel1-01-A-01.tif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CellProfiler/python-bioformats/e5f75fd72ceeedf7ef8ded2ddaedb347fc70c7e1/tests/resources/Channel1-01-A-01.tif -------------------------------------------------------------------------------- /tests/resources/logback.properties: -------------------------------------------------------------------------------- 1 | # Set root logger level to DEBUG and its only appender to A1. 2 | logback.rootLogger=WARN, A1 3 | 4 | # A1 is set to be a ConsoleAppender. 5 | logback.appender.A1=org.apache.logback.ConsoleAppender 6 | 7 | # A1 uses PatternLayout. 8 | logback.appender.A1.layout=org.apache.logback.PatternLayout 9 | logback.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n 10 | -------------------------------------------------------------------------------- /tests/resources/tiff.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 2008-02-05T17:24:46 7 | 17 | 18 | 19 | 20 | 21 | 29 | 30 | 31 | 33 | 34 | 35 | 36 | BIG_TIFF 37 | false 38 | 39 | 40 | 41 | 42 | 43 | 44 | ImageLength 45 | 640 46 | 47 | 48 | 49 | 50 | 51 | 52 | MetaDataPhotometricInterpretation 53 | Monochrome 54 | 55 | 56 | 57 | 58 | 59 | 60 | PhotometricInterpretation 61 | Palette 62 | 63 | 64 | 65 | 66 | 67 | 68 | XResolution 69 | 72 70 | 71 | 72 | 73 | 74 | 75 | 76 | NewSubfileType 77 | 0 78 | 79 | 80 | 81 | 82 | 83 | 84 | DateTime 85 | 2008:02:05 17:24:46 86 | 87 | 88 | 89 | 90 | 91 | 92 | MetaMorph 93 | no 94 | 95 | 96 | 97 | 98 | 99 | 100 | Software 101 | Adobe Photoshop CS2 Macintosh 102 | 103 | 104 | 105 | 106 | 107 | 108 | YResolution 109 | 72 110 | 111 | 112 | 113 | 114 | 115 | 116 | PIXEL_X_DIMENSION 117 | 640 118 | 119 | 120 | 121 | 122 | 123 | 124 | ResolutionUnit 125 | Inch 126 | 127 | 128 | 129 | 130 | 131 | 132 | PIXEL_Y_DIMENSION 133 | 640 134 | 135 | 136 | 137 | 138 | 139 | 140 | Compression 141 | Uncompressed 142 | 143 | 144 | 145 | 146 | 147 | 148 | LITTLE_ENDIAN 149 | false 150 | 151 | 152 | 153 | 154 | 155 | 156 | COLOR_SPACE 157 | 1 158 | 159 | 160 | 161 | 162 | 163 | 164 | BitsPerSample 165 | 8 166 | 167 | 168 | 169 | 170 | 171 | 172 | NumberOfChannels 173 | 3 174 | 175 | 176 | 177 | 178 | 179 | 180 | Orientation 181 | 1st row - top; 1st column - left 182 | 183 | 184 | 185 | 186 | 187 | 188 | SamplesPerPixel 189 | 1 190 | 191 | 192 | 193 | 194 | 195 | 196 | ImageWidth 197 | 640 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /tests/test_formatreader.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | '''test_formatreader.py - test the Bioformats format reader wrapper 9 | 10 | ''' 11 | 12 | from __future__ import absolute_import, unicode_literals 13 | 14 | import os 15 | import re 16 | import unittest 17 | 18 | import future.moves.urllib.request 19 | import javabridge 20 | import numpy 21 | 22 | import bioformats 23 | import bioformats.formatreader 24 | 25 | 26 | def test_make_format_tools_class(): 27 | FormatTools = bioformats.formatreader.make_format_tools_class() 28 | assert FormatTools.CAN_GROUP == 1 29 | assert FormatTools.CANNOT_GROUP == 2 30 | assert FormatTools.DOUBLE == 7 31 | assert FormatTools.FLOAT == 6 32 | assert FormatTools.INT16 == 2 33 | assert FormatTools.INT8 == 0 34 | assert FormatTools.MUST_GROUP == 0 35 | assert FormatTools.UINT16 == 3 36 | assert FormatTools.UINT32 == 5 37 | assert FormatTools.UINT8 == 1 38 | 39 | 40 | def test_make_image_reader(): 41 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 42 | ImageReader = bioformats.formatreader.make_image_reader_class() 43 | FormatTools = bioformats.formatreader.make_format_tools_class() 44 | reader = ImageReader() 45 | reader.setId(path) 46 | assert reader.getDimensionOrder() == "XYCZT" 47 | metadata = javabridge.jdictionary_to_string_dictionary(reader.getMetadata()) 48 | assert int(metadata["ImageWidth"]) == reader.getSizeX() 49 | assert int(metadata["ImageLength"]) == reader.getSizeY() 50 | assert reader.getImageCount() == 1 51 | assert reader.getSizeC() == 1 52 | assert reader.getSizeT() == 1 53 | assert reader.getSizeZ() == 1 54 | assert reader.getPixelType() == FormatTools.UINT8 55 | assert reader.getRGBChannelCount() == 1 56 | 57 | 58 | def test_read_tif(): 59 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 60 | ImageReader = bioformats.formatreader.make_image_reader_class() 61 | FormatTools = bioformats.formatreader.make_format_tools_class() 62 | reader = ImageReader() 63 | reader.setId(path) 64 | data = reader.openBytes(0) 65 | data.shape = (reader.getSizeY(), reader.getSizeX()) 66 | # 67 | # Data as read by cellprofiler.modules.loadimages.load_using_PIL 68 | # 69 | expected_0_10_0_10 = numpy.array( 70 | [[ 0, 7, 7, 6, 5, 8, 4, 2, 1, 2], 71 | [ 0, 8, 8, 7, 6, 10, 4, 2, 2, 2], 72 | [ 0, 9, 9, 7, 8, 8, 2, 1, 3, 2], 73 | [ 0, 10, 9, 8, 10, 6, 2, 2, 3, 2], 74 | [ 0, 10, 10, 10, 9, 4, 2, 2, 2, 2], 75 | [ 0, 9, 9, 10, 8, 3, 2, 4, 2, 2], 76 | [ 0, 9, 9, 10, 8, 2, 2, 4, 3, 2], 77 | [ 0, 9, 8, 9, 7, 4, 2, 2, 2, 2], 78 | [ 0, 10, 11, 9, 9, 4, 2, 2, 2, 2], 79 | [ 0, 12, 13, 12, 9, 4, 2, 2, 2, 2]], dtype=numpy.uint8) 80 | expected_n10_n10 = numpy.array( 81 | [[2, 1, 1, 1, 2, 2, 1, 2, 1, 2], 82 | [1, 2, 2, 2, 2, 1, 1, 1, 2, 1], 83 | [1, 1, 1, 2, 1, 2, 2, 2, 2, 1], 84 | [2, 2, 2, 2, 3, 2, 2, 2, 2, 1], 85 | [1, 2, 2, 1, 1, 1, 1, 1, 2, 2], 86 | [2, 1, 2, 2, 2, 1, 1, 2, 2, 2], 87 | [2, 2, 3, 2, 2, 1, 2, 2, 2, 1], 88 | [3, 3, 1, 2, 2, 2, 2, 3, 2, 2], 89 | [3, 2, 2, 2, 2, 2, 2, 2, 3, 3], 90 | [5, 2, 3, 3, 2, 2, 2, 3, 2, 2]], dtype=numpy.uint8) 91 | assert numpy.all(expected_0_10_0_10 == data[:10, :10]) 92 | assert numpy.all(expected_n10_n10 == data[-10:, -10:]) 93 | 94 | 95 | def test_load_using_bioformats(): 96 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 97 | data = bioformats.formatreader.load_using_bioformats(path, rescale=False) 98 | expected_0_10_0_10 = numpy.array( 99 | [[ 0, 7, 7, 6, 5, 8, 4, 2, 1, 2], 100 | [ 0, 8, 8, 7, 6, 10, 4, 2, 2, 2], 101 | [ 0, 9, 9, 7, 8, 8, 2, 1, 3, 2], 102 | [ 0, 10, 9, 8, 10, 6, 2, 2, 3, 2], 103 | [ 0, 10, 10, 10, 9, 4, 2, 2, 2, 2], 104 | [ 0, 9, 9, 10, 8, 3, 2, 4, 2, 2], 105 | [ 0, 9, 9, 10, 8, 2, 2, 4, 3, 2], 106 | [ 0, 9, 8, 9, 7, 4, 2, 2, 2, 2], 107 | [ 0, 10, 11, 9, 9, 4, 2, 2, 2, 2], 108 | [ 0, 12, 13, 12, 9, 4, 2, 2, 2, 2]], dtype=numpy.uint8) 109 | expected_n10_n10 = numpy.array( 110 | [[2, 1, 1, 1, 2, 2, 1, 2, 1, 2], 111 | [1, 2, 2, 2, 2, 1, 1, 1, 2, 1], 112 | [1, 1, 1, 2, 1, 2, 2, 2, 2, 1], 113 | [2, 2, 2, 2, 3, 2, 2, 2, 2, 1], 114 | [1, 2, 2, 1, 1, 1, 1, 1, 2, 2], 115 | [2, 1, 2, 2, 2, 1, 1, 2, 2, 2], 116 | [2, 2, 3, 2, 2, 1, 2, 2, 2, 1], 117 | [3, 3, 1, 2, 2, 2, 2, 3, 2, 2], 118 | [3, 2, 2, 2, 2, 2, 2, 2, 3, 3], 119 | [5, 2, 3, 3, 2, 2, 2, 3, 2, 2]], dtype=numpy.uint8) 120 | assert numpy.all(expected_0_10_0_10 == data[:10, :10]) 121 | assert numpy.all(expected_n10_n10 == data[-10:, -10:]) 122 | 123 | 124 | def test_read_subimage_tif(): 125 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 126 | with bioformats.ImageReader(path) as f: 127 | data_0_10_0_10 = f.read(XYWH=(0, 0, 10, 10), rescale=False) 128 | 129 | # 130 | # Data as read by cellprofiler.modules.loadimages.load_using_PIL 131 | # 132 | expected_0_10_0_10 = numpy.array( 133 | [[ 0, 7, 7, 6, 5, 8, 4, 2, 1, 2], 134 | [ 0, 8, 8, 7, 6, 10, 4, 2, 2, 2], 135 | [ 0, 9, 9, 7, 8, 8, 2, 1, 3, 2], 136 | [ 0, 10, 9, 8, 10, 6, 2, 2, 3, 2], 137 | [ 0, 10, 10, 10, 9, 4, 2, 2, 2, 2], 138 | [ 0, 9, 9, 10, 8, 3, 2, 4, 2, 2], 139 | [ 0, 9, 9, 10, 8, 2, 2, 4, 3, 2], 140 | [ 0, 9, 8, 9, 7, 4, 2, 2, 2, 2], 141 | [ 0, 10, 11, 9, 9, 4, 2, 2, 2, 2], 142 | [ 0, 12, 13, 12, 9, 4, 2, 2, 2, 2]], dtype=numpy.uint8) 143 | expected_n10_n10 = numpy.array( 144 | [[2, 1, 1, 1, 2, 2, 1, 2, 1, 2], 145 | [1, 2, 2, 2, 2, 1, 1, 1, 2, 1], 146 | [1, 1, 1, 2, 1, 2, 2, 2, 2, 1], 147 | [2, 2, 2, 2, 3, 2, 2, 2, 2, 1], 148 | [1, 2, 2, 1, 1, 1, 1, 1, 2, 2], 149 | [2, 1, 2, 2, 2, 1, 1, 2, 2, 2], 150 | [2, 2, 3, 2, 2, 1, 2, 2, 2, 1], 151 | [3, 3, 1, 2, 2, 2, 2, 3, 2, 2], 152 | [3, 2, 2, 2, 2, 2, 2, 2, 3, 3], 153 | [5, 2, 3, 3, 2, 2, 2, 3, 2, 2]], dtype=numpy.uint8) 154 | assert numpy.all(expected_0_10_0_10 == data_0_10_0_10) 155 | # assert np.all(expected_n10_n10 == data[-10:,-10:]) 156 | 157 | 158 | def test_load_using_bioformats_url(): 159 | url = "https://github.com/CellProfiler/python-bioformats/raw/1.0.5/bioformats/tests/Channel1-01-A-01.tif" 160 | try: 161 | fd = future.moves.urllib.request.urlopen(url) 162 | if fd.code < 200 or fd.code >= 300: 163 | raise OSError("Http error %d" % fd.code) 164 | except OSError as e: 165 | def bad_url(e=e): 166 | raise e 167 | unittest.expectedFailure(bad_url)() 168 | 169 | data = bioformats.formatreader.load_using_bioformats_url(url, rescale=False) 170 | assert data.shape == (640, 640) 171 | 172 | 173 | def test_read_omexml_metadata(): 174 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 175 | xml = bioformats.formatreader.get_omexml_metadata(path) 176 | pattern = r'<\s*Image\s+ID\s*=\s*"Image:0"\s+Name\s*=\s*"Channel1-01-A-01.tif"\s*>' 177 | assert re.search(pattern, xml) 178 | -------------------------------------------------------------------------------- /tests/test_formatwriter.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | from __future__ import absolute_import, unicode_literals 9 | 10 | import numpy 11 | 12 | import bioformats.formatreader 13 | import bioformats.formatwriter 14 | 15 | 16 | def test_write_monochrome_8_bit_tif(tmpdir): 17 | r = numpy.random.RandomState() 18 | r.seed(101) 19 | img = r.randint(0, 256, (11, 33)).astype(numpy.uint8) 20 | path = str(tmpdir.join("monochrome_8_bit.tif")) 21 | bioformats.formatwriter.write_image(path, img, "uint8") 22 | result = bioformats.formatreader.load_using_bioformats(path, rescale=False) 23 | numpy.testing.assert_array_equal(img, result) 24 | 25 | 26 | def test_write_monochrome_16_bit_tif(tmpdir): 27 | r = numpy.random.RandomState() 28 | r.seed(102) 29 | img = r.randint(0, 4096, size=(21, 24)) 30 | path = str(tmpdir.join("monochrome_16_bit.tif")) 31 | bioformats.formatwriter.write_image(path, img, "uint16") 32 | result = bioformats.formatreader.load_using_bioformats(path, rescale=False) 33 | numpy.testing.assert_array_equal(img, result) 34 | 35 | 36 | def test_write_color_tif(tmpdir): 37 | r = numpy.random.RandomState() 38 | r.seed(103) 39 | img = r.randint(0, 256, (9, 11, 3)) 40 | path = str(tmpdir.join("color.tif")) 41 | bioformats.formatwriter.write_image(path, img, "uint8") 42 | result = bioformats.formatreader.load_using_bioformats(path, rescale=False) 43 | numpy.testing.assert_array_equal(img, result) 44 | 45 | 46 | def test_write_movie(tmpdir): 47 | r = numpy.random.RandomState() 48 | r.seed(103) 49 | img = r.randint(0, 256, (7, 23, 11)) 50 | path = str(tmpdir.join("movie.tif")) 51 | for i in range(img.shape[0]): 52 | bioformats.formatwriter.write_image( 53 | path, img[i], "uint8", t=i, size_t=img.shape[0] 54 | ) 55 | for i in range(img.shape[0]): 56 | result = bioformats.formatreader.load_using_bioformats(path, t=i, rescale=False) 57 | numpy.testing.assert_array_equal(img[i], result) 58 | -------------------------------------------------------------------------------- /tests/test_load_using_bioformats.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | from __future__ import absolute_import, print_function, unicode_literals 9 | 10 | import os 11 | 12 | import future.moves.urllib.request 13 | import pytest 14 | 15 | import bioformats 16 | 17 | 18 | def test_load_using_bioformats(): 19 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 20 | image, scale = bioformats.load_image(path, rescale=False, wants_max_intensity=True) 21 | print(image.shape) 22 | 23 | 24 | def test_file_not_found(): 25 | # Regression test of issue #6 26 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel5-01-A-01.tif') 27 | with pytest.raises(IOError): 28 | bioformats.load_image(path) 29 | 30 | 31 | def test_open_file(): 32 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 33 | url = "file:" + future.moves.urllib.request.pathname2url(path) 34 | image, scale = bioformats.load_image_url(url, rescale=False, wants_max_intensity=True) 35 | assert image.shape[0] == 640 36 | 37 | 38 | def test_open_http(): 39 | url = "https://github.com/CellProfiler/python-bioformats/raw/39f2aa8360324b4129284d4f647d4f7ee7797518" \ 40 | "/tests/resources/Channel1-01-A-01.tif" 41 | image, scale = bioformats.load_image_url(url, rescale=False, wants_max_intensity=True) 42 | assert image.shape[0] == 640 43 | 44 | 45 | def test_unicode_url(): 46 | # 47 | # Regression test of issue #17: ensure that this does not 48 | # raise an exception when converting URL to string 49 | # 50 | path = os.path.join(os.path.dirname(__file__), 'resources', 'Channel1-01-A-01.tif') 51 | url = "file:" + future.moves.urllib.request.pathname2url(path) 52 | image, scale = bioformats.load_image_url(url, rescale=False, wants_max_intensity=True) 53 | assert image.shape[0] == 640 54 | 55 | 56 | -------------------------------------------------------------------------------- /tests/test_omexml.py: -------------------------------------------------------------------------------- 1 | # Python-bioformats is distributed under the GNU General Public 2 | # License, but this file is licensed under the more permissive BSD 3 | # license. See the accompanying file LICENSE for details. 4 | # 5 | # Copyright (c) 2009-2014 Broad Institute 6 | # All rights reserved. 7 | 8 | """test_omexml.py read and write OME xml 9 | 10 | """ 11 | 12 | from __future__ import absolute_import, unicode_literals 13 | 14 | import datetime 15 | import os 16 | 17 | import pytest 18 | 19 | import bioformats.omexml 20 | 21 | 22 | @pytest.fixture 23 | def groupfiles_xml(): 24 | path = os.path.join(os.path.split(__file__)[0], "resources", "groupfiles.xml") 25 | fd = open(path) 26 | return fd.read() 27 | 28 | 29 | @pytest.fixture 30 | def tiff_xml(): 31 | path = os.path.join(os.path.split(__file__)[0], "resources", "tiff.xml") 32 | fd = open(path) 33 | return fd.read() 34 | 35 | 36 | def test_00_00_init(): 37 | o = bioformats.omexml.OMEXML() 38 | assert o.root_node.tag == bioformats.omexml.qn(o.get_ns("ome"), "OME") 39 | assert o.image_count == 1 40 | 41 | 42 | def test_01_01_read(groupfiles_xml, tiff_xml): 43 | for xml in (groupfiles_xml, tiff_xml): 44 | o = bioformats.omexml.OMEXML(xml) 45 | 46 | 47 | def test_02_01_iter_children(tiff_xml): 48 | o = bioformats.omexml.OMEXML(tiff_xml) 49 | for node, expected_tag in zip( 50 | o.root_node, 51 | (bioformats.omexml.qn(o.get_ns("ome"), "Image"), 52 | bioformats.omexml.qn(o.get_ns("sa"), "StructuredAnnotations"))): 53 | assert node.tag == expected_tag 54 | 55 | 56 | def test_02_02_get_text(tiff_xml): 57 | o = bioformats.omexml.OMEXML(tiff_xml) 58 | ad = o.root_node.find( 59 | "/".join([bioformats.omexml.qn(o.get_ns('ome'), x) for x in ("Image", "AcquisitionDate")])) 60 | assert bioformats.omexml.get_text(ad) == "2008-02-05T17:24:46" 61 | 62 | 63 | def test_02_04_set_text(tiff_xml): 64 | o = bioformats.omexml.OMEXML(tiff_xml) 65 | ad = o.root_node.find("/".join( 66 | [bioformats.omexml.qn(o.get_ns('ome'), x) for x in ("Image", "AcquisitionDate")])) 67 | im = o.root_node.find(bioformats.omexml.qn(o.get_ns("ome"), "Image")) 68 | bioformats.omexml.set_text(im, "Foo") 69 | assert bioformats.omexml.get_text(im) == "Foo" 70 | bioformats.omexml.set_text(ad, "Bar") 71 | assert bioformats.omexml.get_text(ad) == "Bar" 72 | 73 | 74 | def test_03_01_get_image_count(groupfiles_xml, tiff_xml): 75 | for xml, count in ((groupfiles_xml, 576), (tiff_xml, 1)): 76 | o = bioformats.omexml.OMEXML(xml) 77 | assert o.image_count == count 78 | 79 | 80 | def test_03_02_set_image_count(tiff_xml): 81 | o = bioformats.omexml.OMEXML(tiff_xml) 82 | o.image_count = 2 83 | assert len(o.root_node.findall(bioformats.omexml.qn(o.get_ns("ome"), "Image"))) == 2 84 | 85 | 86 | def test_03_03_image(groupfiles_xml): 87 | o = bioformats.omexml.OMEXML(groupfiles_xml) 88 | assert o.image_count == 576 89 | for i in range(576): 90 | im = o.image(i) 91 | assert im.node.get("ID") == "Image:%d" % i 92 | 93 | 94 | def test_03_04_structured_annotations(tiff_xml): 95 | o = bioformats.omexml.OMEXML(tiff_xml) 96 | assert o.structured_annotations.node.tag == bioformats.omexml.qn(o.get_ns("sa"), "StructuredAnnotations") 97 | 98 | 99 | def test_04_01_image_get_id(tiff_xml): 100 | o = bioformats.omexml.OMEXML(tiff_xml) 101 | assert o.image(0).ID == "Image:0" 102 | 103 | 104 | def test_04_02_image_set_id(tiff_xml): 105 | o = bioformats.omexml.OMEXML(tiff_xml) 106 | o.image(0).ID = "Foo" 107 | assert o.image(0).node.get("ID") == "Foo" 108 | 109 | 110 | def test_04_03_image_get_name(tiff_xml): 111 | o = bioformats.omexml.OMEXML(tiff_xml) 112 | assert o.image(0).Name == "Channel1-01-A-01.tif" 113 | 114 | 115 | def test_04_04_image_set_name(tiff_xml): 116 | o = bioformats.omexml.OMEXML(tiff_xml) 117 | o.image(0).Name = "Foo" 118 | assert o.image(0).node.get("Name") == "Foo" 119 | 120 | 121 | def test_04_05_image_get_acquisition_date(tiff_xml): 122 | o = bioformats.omexml.OMEXML(tiff_xml) 123 | assert o.image(0).AcquisitionDate == "2008-02-05T17:24:46" 124 | 125 | 126 | def test_04_06_image_set_acquisition_date(tiff_xml): 127 | o = bioformats.omexml.OMEXML(tiff_xml) 128 | o.image(0).AcquisitionDate = "2011-12-21T11:04:14.903000" 129 | assert o.image(0).AcquisitionDate == "2011-12-21T11:04:14.903000" 130 | 131 | 132 | def test_04_07_image_1_acquisition_date(): 133 | # regression test of #38 134 | o = bioformats.omexml.OMEXML() 135 | o.set_image_count(2) 136 | date_1 = "2011-12-21T11:04:14.903000" 137 | date_2 = "2015-10-13T09:57:00.000000" 138 | o.image(0).AcquisitionDate = date_1 139 | o.image(1).AcquisitionDate = date_2 140 | assert o.image(0).AcquisitionDate == date_1 141 | assert o.image(1).AcquisitionDate == date_2 142 | 143 | 144 | def test_05_01_pixels_get_id(tiff_xml): 145 | o = bioformats.omexml.OMEXML(tiff_xml) 146 | assert o.image(0).Pixels.ID == "Pixels:0" 147 | 148 | 149 | def test_05_02_pixels_set_id(tiff_xml): 150 | o = bioformats.omexml.OMEXML(tiff_xml) 151 | o.image(0).Pixels.ID = "Foo" 152 | assert o.image(0).Pixels.ID == "Foo" 153 | 154 | 155 | def test_05_03_pixels_get_dimension_order(tiff_xml): 156 | o = bioformats.omexml.OMEXML(tiff_xml) 157 | assert o.image(0).Pixels.DimensionOrder == bioformats.omexml.DO_XYCZT 158 | 159 | 160 | def test_05_04_pixels_set_dimension_order(tiff_xml): 161 | o = bioformats.omexml.OMEXML(tiff_xml) 162 | o.image(0).Pixels.DimensionOrder = bioformats.omexml.DO_XYZCT 163 | assert o.image(0).Pixels.DimensionOrder == bioformats.omexml.DO_XYZCT 164 | 165 | 166 | def test_05_05_pixels_get_pixel_type(tiff_xml): 167 | o = bioformats.omexml.OMEXML(tiff_xml) 168 | assert o.image(0).Pixels.PixelType == bioformats.omexml.PT_UINT8 169 | 170 | 171 | def test_05_06_pixels_set_pixel_type(tiff_xml): 172 | o = bioformats.omexml.OMEXML(tiff_xml) 173 | o.image(0).Pixels.PixelType = bioformats.omexml.PT_FLOAT 174 | assert o.image(0).Pixels.PixelType == bioformats.omexml.PT_FLOAT 175 | 176 | 177 | def test_05_07_pixels_get_size_x(tiff_xml): 178 | o = bioformats.omexml.OMEXML(tiff_xml) 179 | assert o.image(0).Pixels.SizeX == 640 180 | 181 | 182 | def test_05_08_pixels_set_size_x(tiff_xml): 183 | o = bioformats.omexml.OMEXML(tiff_xml) 184 | o.image(0).Pixels.SizeX = 480 185 | assert o.image(0).Pixels.SizeX == 480 186 | 187 | 188 | def test_05_09_pixels_get_size_y(tiff_xml): 189 | o = bioformats.omexml.OMEXML(tiff_xml) 190 | assert o.image(0).Pixels.SizeY == 512 191 | 192 | 193 | def test_05_10_pixels_set_size_y(tiff_xml): 194 | o = bioformats.omexml.OMEXML(tiff_xml) 195 | o.image(0).Pixels.SizeY = 480 196 | assert o.image(0).Pixels.SizeY == 480 197 | 198 | 199 | def test_05_11_pixels_get_size_z(tiff_xml): 200 | o = bioformats.omexml.OMEXML(tiff_xml) 201 | assert o.image(0).Pixels.SizeZ == 1 202 | 203 | 204 | def test_05_12_pixels_set_size_z(tiff_xml): 205 | o = bioformats.omexml.OMEXML(tiff_xml) 206 | o.image(0).Pixels.SizeZ = 2 207 | assert o.image(0).Pixels.SizeZ == 2 208 | 209 | 210 | def test_05_13_pixels_get_size_c(tiff_xml): 211 | o = bioformats.omexml.OMEXML(tiff_xml) 212 | assert o.image(0).Pixels.SizeC == 2 213 | 214 | 215 | def test_05_14_pixels_set_size_c(tiff_xml): 216 | o = bioformats.omexml.OMEXML(tiff_xml) 217 | o.image(0).Pixels.SizeC = 3 218 | assert o.image(0).Pixels.SizeC == 3 219 | 220 | 221 | def test_05_15_pixels_get_size_t(tiff_xml): 222 | o = bioformats.omexml.OMEXML(tiff_xml) 223 | assert o.image(0).Pixels.SizeT == 3 224 | 225 | 226 | def test_05_16_pixels_set_size_t(tiff_xml): 227 | o = bioformats.omexml.OMEXML(tiff_xml) 228 | o.image(0).Pixels.SizeT = 1 229 | assert o.image(0).Pixels.SizeT == 1 230 | 231 | 232 | def test_05_17_pixels_get_channel_count(tiff_xml): 233 | o = bioformats.omexml.OMEXML(tiff_xml) 234 | assert o.image(0).Pixels.channel_count == 1 235 | 236 | 237 | def test_05_18_pixels_set_channel_count(tiff_xml): 238 | o = bioformats.omexml.OMEXML(tiff_xml) 239 | o.image(0).Pixels.channel_count = 2 240 | assert len(o.image(0).Pixels.node.findall(bioformats.omexml.qn(o.get_ns("ome"), "Channel"))) == 2 241 | 242 | 243 | def test_06_01_channel_get_id(tiff_xml): 244 | o = bioformats.omexml.OMEXML(tiff_xml) 245 | assert o.image(0).Pixels.Channel(0).ID == "Channel:0:0" 246 | 247 | 248 | def test_06_02_channel_set_id(tiff_xml): 249 | o = bioformats.omexml.OMEXML(tiff_xml) 250 | o.image(0).Pixels.Channel(0).ID = "Red" 251 | assert o.image(0).Pixels.Channel(0).ID == "Red" 252 | 253 | 254 | def test_06_03_channel_get_name(tiff_xml): 255 | o = bioformats.omexml.OMEXML(tiff_xml) 256 | assert o.image(0).Pixels.Channel(0).Name == "Actin" 257 | 258 | 259 | def test_06_04_channel_set_Name(tiff_xml): 260 | o = bioformats.omexml.OMEXML(tiff_xml) 261 | o.image(0).Pixels.Channel(0).Name = "PI" 262 | assert o.image(0).Pixels.Channel(0).Name == "PI" 263 | 264 | 265 | def test_06_04_channel_get_samples_per_pixel(tiff_xml): 266 | o = bioformats.omexml.OMEXML(tiff_xml) 267 | assert o.image(0).Pixels.Channel(0).SamplesPerPixel == 1 268 | 269 | 270 | def test_06_04_channel_set_samples_per_pixel(tiff_xml): 271 | o = bioformats.omexml.OMEXML(tiff_xml) 272 | o.image(0).Pixels.Channel(0).SamplesPerPixel = 3 273 | assert o.image(0).Pixels.Channel(0).SamplesPerPixel == 3 274 | 275 | 276 | def test_07_01_sa_get_item(tiff_xml): 277 | o = bioformats.omexml.OMEXML(tiff_xml) 278 | a = o.structured_annotations["Annotation:4"] 279 | assert a.tag, bioformats.omexml.qn(o.get_ns("sa") == "XMLAnnotation") 280 | values = a.findall(bioformats.omexml.qn(o.get_ns("sa"), "Value")) 281 | assert len(values) == 1 282 | oms = values[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "OriginalMetadata")) 283 | assert len(oms) == 1 284 | keys = oms[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "Key")) 285 | assert len(keys) == 1 286 | assert bioformats.omexml.get_text(keys[0]) == "XResolution" 287 | values = oms[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "Value")) 288 | assert len(values) == 1 289 | assert bioformats.omexml.get_text(values[0]) == "72" 290 | 291 | 292 | def test_07_02_01_sa_keys(tiff_xml): 293 | keys = bioformats.omexml.OMEXML(tiff_xml).structured_annotations.keys() 294 | for i in range(21): 295 | assert "Annotation:%d" % i in keys 296 | 297 | 298 | def test_07_02_02_sa_has_key(tiff_xml): 299 | o = bioformats.omexml.OMEXML(tiff_xml) 300 | for i in range(20): 301 | assert "Annotation:%d" % i in o.structured_annotations 302 | assert "Foo" not in o.structured_annotations 303 | 304 | 305 | def test_07_03_om_getitem(tiff_xml): 306 | o = bioformats.omexml.OMEXML(tiff_xml) 307 | assert o.structured_annotations.OriginalMetadata["MetaMorph"] == "no" 308 | 309 | 310 | def test_07_04_01_om_keys(tiff_xml): 311 | o = bioformats.omexml.OMEXML(tiff_xml) 312 | keys = o.structured_annotations.OriginalMetadata.keys() 313 | assert len(keys) == 21 314 | for k in ("DateTime", "Software", "YResolution"): 315 | assert k in keys 316 | 317 | 318 | def test_07_04_02_om_has_key(tiff_xml): 319 | o = bioformats.omexml.OMEXML(tiff_xml) 320 | om = o.structured_annotations.OriginalMetadata 321 | for k in ("DateTime", "Software", "YResolution"): 322 | assert k in om 323 | assert "Foo" not in om 324 | 325 | 326 | def test_07_05_om_setitem(): 327 | o = bioformats.omexml.OMEXML() 328 | o.structured_annotations.OriginalMetadata["Foo"] = "Bar" 329 | sa = o.structured_annotations.node 330 | a = sa.findall(bioformats.omexml.qn(o.get_ns("sa"), "XMLAnnotation")) 331 | assert len(a) == 1 332 | vs = a[0].findall(bioformats.omexml.qn(o.get_ns("sa"), "Value")) 333 | assert len(vs) == 1 334 | om = vs[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "OriginalMetadata")) 335 | assert len(om) == 1 336 | k = om[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "Key")) 337 | assert len(k) == 1 338 | assert bioformats.omexml.get_text(k[0]) == "Foo" 339 | v = om[0].findall(bioformats.omexml.qn(bioformats.omexml.NS_ORIGINAL_METADATA, "Value")) 340 | assert len(v) == 1 341 | assert bioformats.omexml.get_text(v[0]) == "Bar" 342 | 343 | 344 | def test_08_01_get_plate(groupfiles_xml): 345 | o = bioformats.omexml.OMEXML(groupfiles_xml) 346 | plate = o.plates[0] 347 | assert plate.ID == "Plate:0" 348 | 349 | 350 | def test_08_02_get_plate_count(groupfiles_xml): 351 | o = bioformats.omexml.OMEXML(groupfiles_xml) 352 | assert len(o.plates) == 1 353 | 354 | 355 | def test_08_02_new_plate(groupfiles_xml): 356 | o = bioformats.omexml.OMEXML(groupfiles_xml) 357 | o.plates.newPlate("MyPlate", "Plate:1") 358 | assert o.plates[1].ID == "Plate:1" 359 | assert o.plates[1].Name == "MyPlate" 360 | 361 | 362 | def test_08_03_plate_iter(groupfiles_xml): 363 | o = bioformats.omexml.OMEXML(groupfiles_xml) 364 | nplates = 5 365 | for i in range(1, nplates): 366 | o.plates.newPlate("MyPlate%d" % i, "Plate:%d" % i) 367 | for i, plate in enumerate(o.plates): 368 | assert plate.ID == "Plate:%d" % i 369 | 370 | 371 | def test_08_04_plate_slice(groupfiles_xml): 372 | o = bioformats.omexml.OMEXML(groupfiles_xml) 373 | for i in range(1, 5): 374 | o.plates.newPlate("MyPlate%d" % i, "Plate:%d" % i) 375 | plates = o.plates[2:-1] 376 | assert len(plates) == 2 377 | assert all([plate.ID == "Plate:%d" % (i + 2) for i, plate in enumerate(plates)]) 378 | 379 | plates = o.plates[-4:4] 380 | assert len(plates) == 3 381 | assert all([plate.ID == "Plate:%d" % (i + 1) for i, plate in enumerate(plates)]) 382 | 383 | 384 | def test_09_01_plate_get_name(groupfiles_xml): 385 | o = bioformats.omexml.OMEXML(groupfiles_xml) 386 | assert o.plates[0].Name == "TimePoint_1" 387 | 388 | 389 | def test_09_02_plate_set_status(): 390 | o = bioformats.omexml.OMEXML() 391 | plate = o.plates.newPlate("Foo", "Bar") 392 | plate.Status = "Gronked" 393 | assert plate.node.get("Status") == "Gronked" 394 | 395 | 396 | def test_09_03_plate_get_status(): 397 | o = bioformats.omexml.OMEXML() 398 | plate = o.plates.newPlate("Foo", "Bar") 399 | plate.node.set("Status", "Gronked") 400 | assert plate.Status == "Gronked" 401 | 402 | 403 | def test_09_04_plate_get_external_identifier(): 404 | o = bioformats.omexml.OMEXML() 405 | plate = o.plates.newPlate("Foo", "Bar") 406 | plate.node.set("ExternalIdentifier", "xyz") 407 | assert plate.ExternalIdentifier == "xyz" 408 | 409 | 410 | def test_09_05_plate_set_external_identifier(): 411 | o = bioformats.omexml.OMEXML() 412 | plate = o.plates.newPlate("Foo", "Bar") 413 | plate.ExternalIdentifier = "xyz" 414 | assert plate.node.get("ExternalIdentifier") == "xyz" 415 | 416 | 417 | def test_09_06_plate_get_column_naming_convention(): 418 | o = bioformats.omexml.OMEXML() 419 | plate = o.plates.newPlate("Foo", "Bar") 420 | plate.node.set("ColumnNamingConvention", bioformats.omexml.NC_LETTER) 421 | assert plate.ColumnNamingConvention == bioformats.omexml.NC_LETTER 422 | 423 | 424 | def test_09_07_plate_set_column_naming_convention(): 425 | o = bioformats.omexml.OMEXML() 426 | plate = o.plates.newPlate("Foo", "Bar") 427 | plate.ColumnNamingConvention = bioformats.omexml.NC_NUMBER 428 | assert plate.ColumnNamingConvention == bioformats.omexml.NC_NUMBER 429 | 430 | 431 | def test_09_08_plate_get_row_naming_convention(): 432 | o = bioformats.omexml.OMEXML() 433 | plate = o.plates.newPlate("Foo", "Bar") 434 | plate.node.set("RowNamingConvention", bioformats.omexml.NC_LETTER) 435 | assert plate.RowNamingConvention == bioformats.omexml.NC_LETTER 436 | 437 | 438 | def test_09_09_plate_set_row_naming_convention(): 439 | o = bioformats.omexml.OMEXML() 440 | plate = o.plates.newPlate("Foo", "Bar") 441 | plate.RowNamingConvention = bioformats.omexml.NC_NUMBER 442 | assert plate.RowNamingConvention == bioformats.omexml.NC_NUMBER 443 | 444 | 445 | def test_09_10_plate_get_well_origin_x(): 446 | o = bioformats.omexml.OMEXML() 447 | plate = o.plates.newPlate("Foo", "Bar") 448 | plate.node.set("WellOriginX", "4.8") 449 | assert plate.WellOriginX == 4.8 450 | 451 | 452 | def test_09_11_plate_set_well_origin_x(): 453 | o = bioformats.omexml.OMEXML() 454 | plate = o.plates.newPlate("Foo", "Bar") 455 | plate.WellOriginX = 3.5 456 | assert plate.node.get("WellOriginX") == "3.5" 457 | 458 | 459 | def test_09_12_plate_get_well_origin_y(): 460 | o = bioformats.omexml.OMEXML() 461 | plate = o.plates.newPlate("Foo", "Bar") 462 | plate.node.set("WellOriginY", "5.8") 463 | assert plate.WellOriginY == 5.8 464 | 465 | 466 | def test_09_13_plate_set_well_origin_y(): 467 | o = bioformats.omexml.OMEXML() 468 | plate = o.plates.newPlate("Foo", "Bar") 469 | plate.WellOriginY = 3.5 470 | assert plate.node.get("WellOriginY") == "3.5" 471 | 472 | 473 | def test_09_14_plate_get_rows(): 474 | o = bioformats.omexml.OMEXML() 475 | plate = o.plates.newPlate("Foo", "Bar") 476 | plate.node.set("Rows", "8") 477 | assert plate.Rows == 8 478 | 479 | 480 | def test_09_15_plate_set_rows(): 481 | o = bioformats.omexml.OMEXML() 482 | plate = o.plates.newPlate("Foo", "Bar") 483 | plate.Rows = 16 484 | assert plate.node.get("Rows") == "16" 485 | 486 | 487 | def test_09_16_plate_get_columns(): 488 | o = bioformats.omexml.OMEXML() 489 | plate = o.plates.newPlate("Foo", "Bar") 490 | plate.node.set("Columns", "12") 491 | assert plate.Columns == 12 492 | 493 | 494 | def test_09_15_plate_set_columns(): 495 | o = bioformats.omexml.OMEXML() 496 | plate = o.plates.newPlate("Foo", "Bar") 497 | plate.Columns = 24 498 | assert plate.node.get("Columns") == "24" 499 | 500 | 501 | def test_10_01_wells_len(groupfiles_xml): 502 | o = bioformats.omexml.OMEXML(groupfiles_xml) 503 | assert len(o.plates[0].Well) == 96 504 | 505 | 506 | def test_10_02_wells_by_row_and_column(groupfiles_xml): 507 | o = bioformats.omexml.OMEXML(groupfiles_xml) 508 | w = o.plates[0].Well[1, 3] 509 | assert w.ID == "Well:0:15" 510 | 511 | 512 | def test_10_03_wells_by_index(groupfiles_xml): 513 | o = bioformats.omexml.OMEXML(groupfiles_xml) 514 | w = o.plates[0].Well[2] 515 | assert w.ID == "Well:0:2" 516 | 517 | 518 | def test_10_04_wells_by_name(groupfiles_xml): 519 | o = bioformats.omexml.OMEXML(groupfiles_xml) 520 | w = o.plates[0].Well["C05"] 521 | assert w.Row == 2 522 | assert w.Column == 4 523 | 524 | 525 | def test_10_05_wells_by_id(groupfiles_xml): 526 | o = bioformats.omexml.OMEXML(groupfiles_xml) 527 | w = o.plates[0].Well["Well:0:3"] 528 | assert w.Row == 0 529 | assert w.Column == 3 530 | 531 | 532 | def test_10_06_wells_by_slice(groupfiles_xml): 533 | o = bioformats.omexml.OMEXML(groupfiles_xml) 534 | for i, w in enumerate(o.plates[0].Well[1::12]): 535 | assert w.Column == 1 536 | assert w.Row == i 537 | 538 | 539 | def test_10_07_iter_wells(groupfiles_xml): 540 | o = bioformats.omexml.OMEXML(groupfiles_xml) 541 | for i, name in enumerate(o.plates[0].Well): 542 | row = int(i / 12) 543 | column = i % 12 544 | assert name == "ABCDEFGH"[row] + "%02d" % (column + 1) 545 | assert name == "H12" 546 | 547 | 548 | def test_10_08_new_well(): 549 | o = bioformats.omexml.OMEXML() 550 | plate = o.plates.newPlate("Foo", "Bar") 551 | plate.Well.new(4, 5, "xyz") 552 | w = plate.Well[0] 553 | assert w.node.get("Row") == "4" 554 | assert w.node.get("Column") == "5" 555 | assert w.node.get("ID") == "xyz" 556 | 557 | 558 | def test_11_01_get_Column(groupfiles_xml): 559 | o = bioformats.omexml.OMEXML(groupfiles_xml) 560 | assert o.plates[0].Well["B05"].Column == 4 561 | 562 | 563 | def test_11_02_get_Row(groupfiles_xml): 564 | o = bioformats.omexml.OMEXML(groupfiles_xml) 565 | assert o.plates[0].Well["B05"].Row == 1 566 | 567 | 568 | def test_11_03_get_external_description(): 569 | o = bioformats.omexml.OMEXML() 570 | plate = o.plates.newPlate("foo", "bar") 571 | w = plate.Well.new(4, 5, "xyz") 572 | w.node.set("ExternalDescription", "ijk") 573 | assert w.ExternalDescription == "ijk" 574 | 575 | 576 | def test_11_04_set_external_description(): 577 | o = bioformats.omexml.OMEXML() 578 | plate = o.plates.newPlate("foo", "bar") 579 | w = plate.Well.new(4, 5, "xyz") 580 | w.ExternalDescription = "LMO" 581 | assert w.node.get("ExternalDescription") == "LMO" 582 | 583 | 584 | def test_11_05_get_external_identifier(): 585 | o = bioformats.omexml.OMEXML() 586 | plate = o.plates.newPlate("foo", "bar") 587 | w = plate.Well.new(4, 5, "xyz") 588 | w.node.set("ExternalIdentifier", "ijk") 589 | assert w.ExternalIdentifier == "ijk" 590 | 591 | 592 | def test_11_06_set_external_identifier(): 593 | o = bioformats.omexml.OMEXML() 594 | plate = o.plates.newPlate("foo", "bar") 595 | w = plate.Well.new(4, 5, "xyz") 596 | w.ExternalIdentifier = "LMO" 597 | assert w.node.get("ExternalIdentifier") == "LMO" 598 | 599 | 600 | def test_12_01_get_sample_len(groupfiles_xml): 601 | o = bioformats.omexml.OMEXML(groupfiles_xml) 602 | assert len(o.plates[0].Well[0].Sample) == 6 603 | 604 | 605 | def test_12_02_get_sample_item(groupfiles_xml): 606 | o = bioformats.omexml.OMEXML(groupfiles_xml) 607 | s = o.plates[0].Well[0].Sample[2] 608 | assert s.node.get("ID") == "WellSample:0:0:2" 609 | 610 | 611 | def test_12_03_get_sample_item_slice(groupfiles_xml): 612 | o = bioformats.omexml.OMEXML(groupfiles_xml) 613 | for i, s in enumerate(o.plates[0].Well[0].Sample[1::2]): 614 | assert s.node.get("ID") == "WellSample:0:0:%d" % (i * 2 + 1) 615 | 616 | 617 | def test_12_04_iter_sample_item(groupfiles_xml): 618 | o = bioformats.omexml.OMEXML(groupfiles_xml) 619 | for i, s in enumerate(o.plates[0].Well[0].Sample): 620 | assert s.node.get("ID") == "WellSample:0:0:%d" % i 621 | 622 | 623 | def test_12_05_new_sample_item(): 624 | o = bioformats.omexml.OMEXML() 625 | plate = o.plates.newPlate("foo", "bar") 626 | w = plate.Well.new(4, 5, "xyz") 627 | w.Sample.new("ooo") 628 | w.Sample.new("ppp") 629 | sample_nodes = w.node.findall(bioformats.omexml.qn(o.get_ns("spw"), "WellSample")) 630 | assert len(sample_nodes) == 2 631 | assert sample_nodes[0].get("ID") == "ooo" 632 | assert sample_nodes[1].get("ID") == "ppp" 633 | assert sample_nodes[0].get("Index") == "0" 634 | assert sample_nodes[1].get("Index") == "1" 635 | 636 | 637 | def test_13_01_get_sample_id(groupfiles_xml): 638 | o = bioformats.omexml.OMEXML(groupfiles_xml) 639 | assert o.plates[0].Well['A02'].Sample[3].ID == "WellSample:0:1:3" 640 | 641 | 642 | def test_13_02_set_sample_id(groupfiles_xml): 643 | o = bioformats.omexml.OMEXML(groupfiles_xml) 644 | ws = o.plates[0].Well['A02'].Sample[3] 645 | ws.ID = "Foo" 646 | assert ws.node.get("ID") == "Foo" 647 | 648 | 649 | def test_13_03_get_position_x(groupfiles_xml): 650 | o = bioformats.omexml.OMEXML(groupfiles_xml) 651 | assert o.plates[0].Well['A01'].Sample[4].PositionX == 402.5 652 | 653 | 654 | def test_13_04_set_position_x(groupfiles_xml): 655 | o = bioformats.omexml.OMEXML(groupfiles_xml) 656 | ws = o.plates[0].Well['A02'].Sample[3] 657 | ws.PositionX = 201.75 658 | assert ws.node.get("PositionX") == "201.75" 659 | 660 | 661 | def test_13_05_get_position_y(groupfiles_xml): 662 | o = bioformats.omexml.OMEXML(groupfiles_xml) 663 | assert o.plates[0].Well['A01'].Sample[4].PositionY == 204.25 664 | 665 | 666 | def test_13_06_set_position_y(groupfiles_xml): 667 | o = bioformats.omexml.OMEXML(groupfiles_xml) 668 | ws = o.plates[0].Well['A02'].Sample[3] 669 | ws.PositionY = 14.5 670 | assert ws.node.get("PositionY") == "14.5" 671 | 672 | 673 | def test_13_07_get_timepoint(groupfiles_xml): 674 | o = bioformats.omexml.OMEXML(groupfiles_xml) 675 | assert o.plates[0].Well['A01'].Sample[1].Timepoint == '2011-12-27T08:24:29.960000' 676 | 677 | 678 | def test_13_08_set_timepoint(groupfiles_xml): 679 | o = bioformats.omexml.OMEXML(groupfiles_xml) 680 | ws = o.plates[0].Well['A02'].Sample[3] 681 | now = datetime.datetime.now() 682 | now_string = now.isoformat() 683 | ws.Timepoint = now 684 | assert ws.node.get("Timepoint") == now_string 685 | ws = o.plates[0].Well['A03'].Sample[4] 686 | ws.Timepoint = now_string 687 | assert ws.node.get("Timepoint") == now_string 688 | 689 | 690 | def test_13_09_get_index(groupfiles_xml): 691 | o = bioformats.omexml.OMEXML(groupfiles_xml) 692 | ws = o.plates[0].Well['A02'].Sample[3] 693 | assert ws.Index == 9 694 | 695 | 696 | def test_13_10_set_index(groupfiles_xml): 697 | o = bioformats.omexml.OMEXML(groupfiles_xml) 698 | ws = o.plates[0].Well['A02'].Sample[3] 699 | ws.Index = 301 700 | assert ws.Index == 301 701 | 702 | 703 | def test_13_11_get_image_ref(groupfiles_xml): 704 | o = bioformats.omexml.OMEXML(groupfiles_xml) 705 | ws = o.plates[0].Well['A02'].Sample[3] 706 | assert ws.ImageRef == "Image:9" 707 | ref = ws.node.find(bioformats.omexml.qn(o.get_ns("spw"), "ImageRef")) 708 | ws.node.remove(ref) 709 | assert ws.ImageRef is None 710 | 711 | 712 | def test_13_12_set_image_ref(groupfiles_xml): 713 | o = bioformats.omexml.OMEXML(groupfiles_xml) 714 | ws = o.plates[0].Well['A02'].Sample[3] 715 | ws.ImageRef = "Foo" 716 | assert ws.node.find(bioformats.omexml.qn(o.get_ns("spw"), "ImageRef")).get("ID") == "Foo" 717 | 718 | 719 | def test_14_01_get_plane_count(tiff_xml): 720 | o = bioformats.omexml.OMEXML(tiff_xml) 721 | assert o.image(0).Pixels.plane_count == 1 722 | 723 | 724 | def test_14_02_set_plane_count(): 725 | o = bioformats.omexml.OMEXML() 726 | pixels = o.image(0).Pixels 727 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 728 | pixels.plane_count = 2 729 | assert len(pixels.node.findall(bioformats.omexml.qn(o.get_ns('ome'), "Plane"))) == 2 730 | 731 | 732 | def test_14_03_get_the_c(tiff_xml): 733 | o = bioformats.omexml.OMEXML(tiff_xml) 734 | pixels = o.image(0).Pixels 735 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 736 | plane = pixels.Plane(0) 737 | assert isinstance(plane, bioformats.omexml.OMEXML.Plane) 738 | plane.node.set("TheC", "15") 739 | assert plane.TheC == 15 740 | 741 | 742 | def test_14_04_get_the_z(tiff_xml): 743 | o = bioformats.omexml.OMEXML(tiff_xml) 744 | pixels = o.image(0).Pixels 745 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 746 | plane = pixels.Plane(0) 747 | assert isinstance(plane, bioformats.omexml.OMEXML.Plane) 748 | plane.node.set("TheZ", "10") 749 | assert plane.TheZ == 10 750 | 751 | 752 | def test_14_05_get_the_t(tiff_xml): 753 | o = bioformats.omexml.OMEXML(tiff_xml) 754 | pixels = o.image(0).Pixels 755 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 756 | plane = pixels.Plane(0) 757 | assert isinstance(plane, bioformats.omexml.OMEXML.Plane) 758 | plane.node.set("TheT", "9") 759 | assert plane.TheT == 9 760 | 761 | 762 | def test_14_06_set_the_c(tiff_xml): 763 | o = bioformats.omexml.OMEXML(tiff_xml) 764 | pixels = o.image(0).Pixels 765 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 766 | plane = pixels.Plane(0) 767 | plane.TheC = 5 768 | assert int(plane.node.get("TheC")) == 5 769 | 770 | 771 | def test_14_07_set_the_z(tiff_xml): 772 | o = bioformats.omexml.OMEXML(tiff_xml) 773 | pixels = o.image(0).Pixels 774 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 775 | plane = pixels.Plane(0) 776 | plane.TheZ = 6 777 | assert int(plane.node.get("TheZ")) == 6 778 | 779 | 780 | def test_14_08_set_the_t(tiff_xml): 781 | o = bioformats.omexml.OMEXML(tiff_xml) 782 | pixels = o.image(0).Pixels 783 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 784 | plane = pixels.Plane(0) 785 | plane.TheC = 7 786 | assert int(plane.node.get("TheC")) == 7 787 | 788 | 789 | def test_14_09_get_delta_t(tiff_xml): 790 | o = bioformats.omexml.OMEXML(tiff_xml) 791 | pixels = o.image(0).Pixels 792 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 793 | plane = pixels.Plane(0) 794 | assert plane.DeltaT == 1.25 795 | 796 | 797 | def test_14_10_get_exposure_time(tiff_xml): 798 | o = bioformats.omexml.OMEXML(tiff_xml) 799 | pixels = o.image(0).Pixels 800 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 801 | plane = pixels.Plane(0) 802 | assert plane.get_ExposureTime() == 0.25 803 | 804 | 805 | def test_14_11_get_position_x(tiff_xml): 806 | o = bioformats.omexml.OMEXML(tiff_xml) 807 | pixels = o.image(0).Pixels 808 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 809 | plane = pixels.Plane(0) 810 | assert plane.PositionX == 3.5 811 | 812 | 813 | def test_14_12_get_position_y(tiff_xml): 814 | o = bioformats.omexml.OMEXML(tiff_xml) 815 | pixels = o.image(0).Pixels 816 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 817 | plane = pixels.Plane(0) 818 | assert plane.PositionY == 4.75 819 | 820 | 821 | def test_14_13_get_position_z(tiff_xml): 822 | o = bioformats.omexml.OMEXML(tiff_xml) 823 | pixels = o.image(0).Pixels 824 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 825 | plane = pixels.Plane(0) 826 | assert plane.PositionZ == 2.25 827 | 828 | 829 | def test_14_14_set_delta_t(tiff_xml): 830 | o = bioformats.omexml.OMEXML(tiff_xml) 831 | pixels = o.image(0).Pixels 832 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 833 | plane = pixels.Plane(0) 834 | plane.DeltaT = 1.25 835 | assert float(plane.node.get("DeltaT")) == 1.25 836 | 837 | 838 | def test_14_15_set_position_x(tiff_xml): 839 | o = bioformats.omexml.OMEXML(tiff_xml) 840 | pixels = o.image(0).Pixels 841 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 842 | plane = pixels.Plane(0) 843 | plane.PositionX = 5.5 844 | assert float(plane.node.get("PositionX")) == 5.5 845 | 846 | 847 | def test_14_16_set_position_y(tiff_xml): 848 | o = bioformats.omexml.OMEXML(tiff_xml) 849 | pixels = o.image(0).Pixels 850 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 851 | plane = pixels.Plane(0) 852 | plane.PositionY = 6.5 853 | assert float(plane.node.get("PositionY")) == 6.5 854 | 855 | 856 | def test_14_17_set_position_z(tiff_xml): 857 | o = bioformats.omexml.OMEXML(tiff_xml) 858 | pixels = o.image(0).Pixels 859 | assert isinstance(pixels, bioformats.omexml.OMEXML.Pixels) 860 | plane = pixels.Plane(0) 861 | plane.PositionZ = 7.5 862 | assert float(plane.node.get("PositionZ")) == 7.5 863 | --------------------------------------------------------------------------------