diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000000000000000000000000000000000..bf0919262fbc7f5bb44035900379d0a110d3bd38 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,595 @@ +GNU General Public License +========================== + +_Version 3, 29 June 2007_ +_Copyright (C) 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_ + +Everyone is permitted to copy and distribute verbatim copies of this license +document, but changing it is not allowed. + +## Preamble + +The GNU General Public License is a free, copyleft license for software and other +kinds of works. + +The licenses for most software and other practical works are designed to take away +your freedom to share and change the works. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change all versions of a +program--to make sure it remains free software for all its users. We, the Free +Software Foundation, use the GNU General Public License for most of our software; it +applies also to any other work released this way by its authors. You can apply it to +your programs, too. + +When we speak of free software, we are referring to freedom, not price. Our General +Public Licenses are designed to make sure that you have the freedom to distribute +copies of free software (and charge for them if you wish), that you receive source +code or can get it if you want it, that you can change the software or use pieces of +it in new free programs, and that you know you can do these things. + +To protect your rights, we need to prevent others from denying you these rights or +asking you to surrender the rights. Therefore, you have certain responsibilities if +you distribute copies of the software, or if you modify it: responsibilities to +respect the freedom of others. + +For example, if you distribute copies of such a program, whether gratis or for a fee, +you must pass on to the recipients the same freedoms that you received. You must make +sure that they, too, receive or can get the source code. And you must show them these +terms so they know their rights. + +Developers that use the GNU GPL protect your rights with two steps: **(1)** assert +copyright on the software, and **(2)** offer you this License giving you legal permission +to copy, distribute and/or modify it. + +For the developers' and authors' protection, the GPL clearly explains that there is +no warranty for this free software. For both users' and authors' sake, the GPL +requires that modified versions be marked as changed, so that their problems will not +be attributed erroneously to authors of previous versions. + +Some devices are designed to deny users access to install or run modified versions of +the software inside them, although the manufacturer can do so. This is fundamentally +incompatible with the aim of protecting users' freedom to change the software. The +systematic pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we have designed +this version of the GPL to prohibit the practice for those products. If such problems +arise substantially in other domains, we stand ready to extend this provision to +those domains in future versions of the GPL, as needed to protect the freedom of +users. + +Finally, every program is threatened constantly by software patents. States should +not allow patents to restrict development and use of software on general-purpose +computers, but in those that do, we wish to avoid the special danger that patents +applied to a free program could make it effectively proprietary. To prevent this, the +GPL assures that patents cannot be used to render the program non-free. + +The precise terms and conditions for copying, distribution and modification follow. + +## TERMS AND CONDITIONS + +### 0. Definitions + +"This License" refers to version 3 of the GNU General Public License. + +"Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + +"The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + +To "modify" a work means to copy from or adapt all or part of the work in +a fashion requiring copyright permission, other than the making of an exact copy. The +resulting work is called a "modified version" of the earlier work or a +work "based on" the earlier work. + +A "covered work" means either the unmodified Program or a work based on +the Program. + +To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for infringement under +applicable copyright law, except executing it on a computer or modifying a private +copy. Propagation includes copying, distribution (with or without modification), +making available to the public, and in some countries other activities as well. + +To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through a computer +network, with no transfer of a copy, is not conveying. + +An interactive user interface displays "Appropriate Legal Notices" to the +extent that it includes a convenient and prominently visible feature that **(1)** +displays an appropriate copyright notice, and **(2)** tells the user that there is no +warranty for the work (except to the extent that warranties are provided), that +licensees may convey the work under this License, and how to view a copy of this +License. If the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + +### 1. Source Code + +The "source code" for a work means the preferred form of the work for +making modifications to it. "Object code" means any non-source form of a +work. + +A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of interfaces +specified for a particular programming language, one that is widely used among +developers working in that language. + +The "System Libraries" of an executable work include anything, other than +the work as a whole, that **(a)** is included in the normal form of packaging a Major +Component, but which is not part of that Major Component, and **(b)** serves only to +enable use of the work with that Major Component, or to implement a Standard +Interface for which an implementation is available to the public in source code form. +A "Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system (if any) on which +the executable work runs, or a compiler used to produce the work, or an object code +interpreter used to run it. + +The "Corresponding Source" for a work in object code form means all the +source code needed to generate, install, and (for an executable work) run the object +code and to modify the work, including scripts to control those activities. However, +it does not include the work's System Libraries, or general-purpose tools or +generally available free programs which are used unmodified in performing those +activities but which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for the work, and +the source code for shared libraries and dynamically linked subprograms that the work +is specifically designed to require, such as by intimate data communication or +control flow between those subprograms and other parts of the work. + +The Corresponding Source need not include anything that users can regenerate +automatically from other parts of the Corresponding Source. + +The Corresponding Source for a work in source code form is that same work. + +### 2. Basic Permissions + +All rights granted under this License are granted for the term of copyright on the +Program, and are irrevocable provided the stated conditions are met. This License +explicitly affirms your unlimited permission to run the unmodified Program. The +output from running a covered work is covered by this License only if the output, +given its content, constitutes a covered work. This License acknowledges your rights +of fair use or other equivalent, as provided by copyright law. + +You may make, run and propagate covered works that you do not convey, without +conditions so long as your license otherwise remains in force. You may convey covered +works to others for the sole purpose of having them make modifications exclusively +for you, or provide you with facilities for running those works, provided that you +comply with the terms of this License in conveying all material for which you do not +control copyright. Those thus making or running the covered works for you must do so +exclusively on your behalf, under your direction and control, on terms that prohibit +them from making any copies of your copyrighted material outside their relationship +with you. + +Conveying under any other circumstances is permitted solely under the conditions +stated below. Sublicensing is not allowed; section 10 makes it unnecessary. + +### 3. Protecting Users' Legal Rights From Anti-Circumvention Law + +No covered work shall be deemed part of an effective technological measure under any +applicable law fulfilling obligations under article 11 of the WIPO copyright treaty +adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention +of such measures. + +When you convey a covered work, you waive any legal power to forbid circumvention of +technological measures to the extent such circumvention is effected by exercising +rights under this License with respect to the covered work, and you disclaim any +intention to limit operation or modification of the work as a means of enforcing, +against the work's users, your or third parties' legal rights to forbid circumvention +of technological measures. + +### 4. Conveying Verbatim Copies + +You may convey verbatim copies of the Program's source code as you receive it, in any +medium, provided that you conspicuously and appropriately publish on each copy an +appropriate copyright notice; keep intact all notices stating that this License and +any non-permissive terms added in accord with section 7 apply to the code; keep +intact all notices of the absence of any warranty; and give all recipients a copy of +this License along with the Program. + +You may charge any price or no price for each copy that you convey, and you may offer +support or warranty protection for a fee. + +### 5. Conveying Modified Source Versions + +You may convey a work based on the Program, or the modifications to produce it from +the Program, in the form of source code under the terms of section 4, provided that +you also meet all of these conditions: + +* **a)** The work must carry prominent notices stating that you modified it, and giving a +relevant date. +* **b)** The work must carry prominent notices stating that it is released under this +License and any conditions added under section 7. This requirement modifies the +requirement in section 4 to "keep intact all notices". +* **c)** You must license the entire work, as a whole, under this License to anyone who +comes into possession of a copy. This License will therefore apply, along with any +applicable section 7 additional terms, to the whole of the work, and all its parts, +regardless of how they are packaged. This License gives no permission to license the +work in any other way, but it does not invalidate such permission if you have +separately received it. +* **d)** If the work has interactive user interfaces, each must display Appropriate Legal +Notices; however, if the Program has interactive interfaces that do not display +Appropriate Legal Notices, your work need not make them do so. + +A compilation of a covered work with other separate and independent works, which are +not by their nature extensions of the covered work, and which are not combined with +it such as to form a larger program, in or on a volume of a storage or distribution +medium, is called an "aggregate" if the compilation and its resulting +copyright are not used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work in an aggregate +does not cause this License to apply to the other parts of the aggregate. + +### 6. Conveying Non-Source Forms + +You may convey a covered work in object code form under the terms of sections 4 and +5, provided that you also convey the machine-readable Corresponding Source under the +terms of this License, in one of these ways: + +* **a)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by the Corresponding Source fixed on a +durable physical medium customarily used for software interchange. +* **b)** Convey the object code in, or embodied in, a physical product (including a +physical distribution medium), accompanied by a written offer, valid for at least +three years and valid for as long as you offer spare parts or customer support for +that product model, to give anyone who possesses the object code either **(1)** a copy of +the Corresponding Source for all the software in the product that is covered by this +License, on a durable physical medium customarily used for software interchange, for +a price no more than your reasonable cost of physically performing this conveying of +source, or **(2)** access to copy the Corresponding Source from a network server at no +charge. +* **c)** Convey individual copies of the object code with a copy of the written offer to +provide the Corresponding Source. This alternative is allowed only occasionally and +noncommercially, and only if you received the object code with such an offer, in +accord with subsection 6b. +* **d)** Convey the object code by offering access from a designated place (gratis or for +a charge), and offer equivalent access to the Corresponding Source in the same way +through the same place at no further charge. You need not require recipients to copy +the Corresponding Source along with the object code. If the place to copy the object +code is a network server, the Corresponding Source may be on a different server +(operated by you or a third party) that supports equivalent copying facilities, +provided you maintain clear directions next to the object code saying where to find +the Corresponding Source. Regardless of what server hosts the Corresponding Source, +you remain obligated to ensure that it is available for as long as needed to satisfy +these requirements. +* **e)** Convey the object code using peer-to-peer transmission, provided you inform +other peers where the object code and Corresponding Source of the work are being +offered to the general public at no charge under subsection 6d. + +A separable portion of the object code, whose source code is excluded from the +Corresponding Source as a System Library, need not be included in conveying the +object code work. + +A "User Product" is either **(1)** a "consumer product", which +means any tangible personal property which is normally used for personal, family, or +household purposes, or **(2)** anything designed or sold for incorporation into a +dwelling. In determining whether a product is a consumer product, doubtful cases +shall be resolved in favor of coverage. For a particular product received by a +particular user, "normally used" refers to a typical or common use of +that class of product, regardless of the status of the particular user or of the way +in which the particular user actually uses, or expects or is expected to use, the +product. A product is a consumer product regardless of whether the product has +substantial commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + +"Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install and execute +modified versions of a covered work in that User Product from a modified version of +its Corresponding Source. The information must suffice to ensure that the continued +functioning of the modified object code is in no case prevented or interfered with +solely because modification has been made. + +If you convey an object code work under this section in, or with, or specifically for +use in, a User Product, and the conveying occurs as part of a transaction in which +the right of possession and use of the User Product is transferred to the recipient +in perpetuity or for a fixed term (regardless of how the transaction is +characterized), the Corresponding Source conveyed under this section must be +accompanied by the Installation Information. But this requirement does not apply if +neither you nor any third party retains the ability to install modified object code +on the User Product (for example, the work has been installed in ROM). + +The requirement to provide Installation Information does not include a requirement to +continue to provide support service, warranty, or updates for a work that has been +modified or installed by the recipient, or for the User Product in which it has been +modified or installed. Access to a network may be denied when the modification itself +materially and adversely affects the operation of the network or violates the rules +and protocols for communication across the network. + +Corresponding Source conveyed, and Installation Information provided, in accord with +this section must be in a format that is publicly documented (and with an +implementation available to the public in source code form), and must require no +special password or key for unpacking, reading or copying. + +### 7. Additional Terms + +"Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. Additional +permissions that are applicable to the entire Program shall be treated as though they +were included in this License, to the extent that they are valid under applicable +law. If additional permissions apply only to part of the Program, that part may be +used separately under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + +When you convey a copy of a covered work, you may at your option remove any +additional permissions from that copy, or from any part of it. (Additional +permissions may be written to require their own removal in certain cases when you +modify the work.) You may place additional permissions on material, added by you to a +covered work, for which you have or can give appropriate copyright permission. + +Notwithstanding any other provision of this License, for material you add to a +covered work, you may (if authorized by the copyright holders of that material) +supplement the terms of this License with terms: + +* **a)** Disclaiming warranty or limiting liability differently from the terms of +sections 15 and 16 of this License; or +* **b)** Requiring preservation of specified reasonable legal notices or author +attributions in that material or in the Appropriate Legal Notices displayed by works +containing it; or +* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that +modified versions of such material be marked in reasonable ways as different from the +original version; or +* **d)** Limiting the use for publicity purposes of names of licensors or authors of the +material; or +* **e)** Declining to grant rights under trademark law for use of some trade names, +trademarks, or service marks; or +* **f)** Requiring indemnification of licensors and authors of that material by anyone +who conveys the material (or modified versions of it) with contractual assumptions of +liability to the recipient, for any liability that these contractual assumptions +directly impose on those licensors and authors. + +All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you received +it, or any part of it, contains a notice stating that it is governed by this License +along with a term that is a further restriction, you may remove that term. If a +license document contains a further restriction but permits relicensing or conveying +under this License, you may add to a covered work material governed by the terms of +that license document, provided that the further restriction does not survive such +relicensing or conveying. + +If you add terms to a covered work in accord with this section, you must place, in +the relevant source files, a statement of the additional terms that apply to those +files, or a notice indicating where to find the applicable terms. + +Additional terms, permissive or non-permissive, may be stated in the form of a +separately written license, or stated as exceptions; the above requirements apply +either way. + +### 8. Termination + +You may not propagate or modify a covered work except as expressly provided under +this License. Any attempt otherwise to propagate or modify it is void, and will +automatically terminate your rights under this License (including any patent licenses +granted under the third paragraph of section 11). + +However, if you cease all violation of this License, then your license from a +particular copyright holder is reinstated **(a)** provisionally, unless and until the +copyright holder explicitly and finally terminates your license, and **(b)** permanently, +if the copyright holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + +Moreover, your license from a particular copyright holder is reinstated permanently +if the copyright holder notifies you of the violation by some reasonable means, this +is the first time you have received notice of violation of this License (for any +work) from that copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + +Termination of your rights under this section does not terminate the licenses of +parties who have received copies or rights from you under this License. If your +rights have been terminated and not permanently reinstated, you do not qualify to +receive new licenses for the same material under section 10. + +### 9. Acceptance Not Required for Having Copies + +You are not required to accept this License in order to receive or run a copy of the +Program. Ancillary propagation of a covered work occurring solely as a consequence of +using peer-to-peer transmission to receive a copy likewise does not require +acceptance. However, nothing other than this License grants you permission to +propagate or modify any covered work. These actions infringe copyright if you do not +accept this License. Therefore, by modifying or propagating a covered work, you +indicate your acceptance of this License to do so. + +### 10. Automatic Licensing of Downstream Recipients + +Each time you convey a covered work, the recipient automatically receives a license +from the original licensors, to run, modify and propagate that work, subject to this +License. You are not responsible for enforcing compliance by third parties with this +License. + +An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an organization, or +merging organizations. If propagation of a covered work results from an entity +transaction, each party to that transaction who receives a copy of the work also +receives whatever licenses to the work the party's predecessor in interest had or +could give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if the predecessor +has it or can get it with reasonable efforts. + +You may not impose any further restrictions on the exercise of the rights granted or +affirmed under this License. For example, you may not impose a license fee, royalty, +or other charge for exercise of rights granted under this License, and you may not +initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging +that any patent claim is infringed by making, using, selling, offering for sale, or +importing the Program or any portion of it. + +### 11. Patents + +A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The work thus +licensed is called the contributor's "contributor version". + +A contributor's "essential patent claims" are all patent claims owned or +controlled by the contributor, whether already acquired or hereafter acquired, that +would be infringed by some manner, permitted by this License, of making, using, or +selling its contributor version, but do not include claims that would be infringed +only as a consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant patent +sublicenses in a manner consistent with the requirements of this License. + +Each contributor grants you a non-exclusive, worldwide, royalty-free patent license +under the contributor's essential patent claims, to make, use, sell, offer for sale, +import and otherwise run, modify and propagate the contents of its contributor +version. + +In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent (such as an +express permission to practice a patent or covenant not to sue for patent +infringement). To "grant" such a patent license to a party means to make +such an agreement or commitment not to enforce a patent against the party. + +If you convey a covered work, knowingly relying on a patent license, and the +Corresponding Source of the work is not available for anyone to copy, free of charge +and under the terms of this License, through a publicly available network server or +other readily accessible means, then you must either **(1)** cause the Corresponding +Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the +patent license for this particular work, or **(3)** arrange, in a manner consistent with +the requirements of this License, to extend the patent license to downstream +recipients. "Knowingly relying" means you have actual knowledge that, but +for the patent license, your conveying the covered work in a country, or your +recipient's use of the covered work in a country, would infringe one or more +identifiable patents in that country that you have reason to believe are valid. + +If, pursuant to or in connection with a single transaction or arrangement, you +convey, or propagate by procuring conveyance of, a covered work, and grant a patent +license to some of the parties receiving the covered work authorizing them to use, +propagate, modify or convey a specific copy of the covered work, then the patent +license you grant is automatically extended to all recipients of the covered work and +works based on it. + +A patent license is "discriminatory" if it does not include within the +scope of its coverage, prohibits the exercise of, or is conditioned on the +non-exercise of one or more of the rights that are specifically granted under this +License. You may not convey a covered work if you are a party to an arrangement with +a third party that is in the business of distributing software, under which you make +payment to the third party based on the extent of your activity of conveying the +work, and under which the third party grants, to any of the parties who would receive +the covered work from you, a discriminatory patent license **(a)** in connection with +copies of the covered work conveyed by you (or copies made from those copies), or **(b)** +primarily for and in connection with specific products or compilations that contain +the covered work, unless you entered into that arrangement, or that patent license +was granted, prior to 28 March 2007. + +Nothing in this License shall be construed as excluding or limiting any implied +license or other defenses to infringement that may otherwise be available to you +under applicable patent law. + +### 12. No Surrender of Others' Freedom + +If conditions are imposed on you (whether by court order, agreement or otherwise) +that contradict the conditions of this License, they do not excuse you from the +conditions of this License. If you cannot convey a covered work so as to satisfy +simultaneously your obligations under this License and any other pertinent +obligations, then as a consequence you may not convey it at all. For example, if you +agree to terms that obligate you to collect a royalty for further conveying from +those to whom you convey the Program, the only way you could satisfy both those terms +and this License would be to refrain entirely from conveying the Program. + +### 13. Use with the GNU Affero General Public License + +Notwithstanding any other provision of this License, you have permission to link or +combine any covered work with a work licensed under version 3 of the GNU Affero +General Public License into a single combined work, and to convey the resulting work. +The terms of this License will continue to apply to the part which is the covered +work, but the special requirements of the GNU Affero General Public License, section +13, concerning interaction through a network will apply to the combination as such. + +### 14. Revised Versions of this License + +The Free Software Foundation may publish revised and/or new versions of the GNU +General Public License from time to time. Such new versions will be similar in spirit +to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Program specifies that +a certain numbered version of the GNU General Public License "or any later +version" applies to it, you have the option of following the terms and +conditions either of that numbered version or of any later version published by the +Free Software Foundation. If the Program does not specify a version number of the GNU +General Public License, you may choose any version ever published by the Free +Software Foundation. + +If the Program specifies that a proxy can decide which future versions of the GNU +General Public License can be used, that proxy's public statement of acceptance of a +version permanently authorizes you to choose that version for the Program. + +Later license versions may give you additional or different permissions. However, no +additional obligations are imposed on any author or copyright holder as a result of +your choosing to follow a later version. + +### 15. Disclaimer of Warranty + +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER +EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE +QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE +DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +### 16. Limitation of Liability + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY +COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS +PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, +INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE +OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE +WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +### 17. Interpretation of Sections 15 and 16 + +If the disclaimer of warranty and limitation of liability provided above cannot be +given local legal effect according to their terms, reviewing courts shall apply local +law that most closely approximates an absolute waiver of all civil liability in +connection with the Program, unless a warranty or assumption of liability accompanies +a copy of the Program in return for a fee. + +_END OF TERMS AND CONDITIONS_ + +## How to Apply These Terms to Your New Programs + +If you develop a new program, and you want it to be of the greatest possible use to +the public, the best way to achieve this is to make it free software which everyone +can redistribute and change under these terms. + +To do so, attach the following notices to the program. It is safest to attach them +to the start of each source file to most effectively state the exclusion of warranty; +and each file should have at least the "copyright" line and a pointer to +where the full notice is found. + + Apache Commons Codec - Icy plugin (http://icy.bioimageanalysis.org/). + Copyright (C) 2021 Biological Image Analysis unit, Institut Pasteur + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + +Also add information on how to contact you by electronic and paper mail. + +If the program does terminal interaction, make it output a short notice like this +when it starts in an interactive mode: + + Apache Commons Codec Copyright (C) 2021 Biological Image Analysis unit, Institut Pasteur + This program comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type 'show c' for details. + +The hypothetical commands `show w` and `show c` should show the appropriate parts of +the General Public License. Of course, your program's commands might be different; +for a GUI interface, you would use an "about box". + +You should also get your employer (if you work as a programmer) or school, if any, to +sign a "copyright disclaimer" for the program, if necessary. For more +information on this, and how to apply and follow the GNU GPL, see +<<http://www.gnu.org/licenses/>>. + +The GNU General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may consider it +more useful to permit linking proprietary applications with the library. If this is +what you want to do, use the GNU Lesser General Public License instead of this +License. But first, please read +<<http://www.gnu.org/philosophy/why-not-lgpl.html>>. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..7f2e77ccb0c0daeb6fdfed95762ec5da1badac10 --- /dev/null +++ b/pom.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <!-- Inherited Icy Parent POM --> + <parent> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>parent-pom-plugin</artifactId> + <version>1.0.6</version> + </parent> + + <!-- Project Information --> + <artifactId>matlab-io</artifactId> + <version>2.5.2</version> + + <packaging>jar</packaging> + + <name>Matlab IO</name> + <description> + Low-level library for reading and writing Matlab .mat files. + This library also provides tools to convert the 5D Icy sequences into the Matlab data structure, which is basically a n-dimensional (nD) array. + </description> + <inceptionYear>2022</inceptionYear> + + <organization> + <name>Institut Pasteur</name> + <url>https://pasteur.fr</url> + </organization> + + <licenses> + <license> + <name>GNU GPLv3</name> + <url>https://www.gnu.org/licenses/gpl-3.0.en.html</url> + <distribution>repo</distribution> + </license> + </licenses> + + <developers> + <developer> + <id>ylemontag</id> + <name>Yoann Le Montagner</name> + <url>https://icy.bioimageanalysis.org/author/ylemontag/</url> + <roles> + <role>developer</role> + <role>maintainer</role> + </roles> + </developer> + </developers> + + <!-- Project properties --> + <properties> + + </properties> + + <!-- Project build configuration --> + <build> + <plugins> + </plugins> + </build> + + <!-- List of project's dependencies --> + <dependencies> + <!-- The core of Icy --> + <dependency> + <groupId>org.bioimageanalysis.icy</groupId> + <artifactId>icy-kernel</artifactId> + </dependency> + + </dependencies> + + <!-- Icy Maven repository (to find parent POM) --> + <repositories> + <repository> + <id>icy</id> + <name>Icy's Nexus</name> + <url>https://icy-nexus.pasteur.fr/repository/Icy/</url> + </repository> + </repositories> +</project> diff --git a/src/main/java/plugins/ylemontag/matlabio/ComplexMode.java b/src/main/java/plugins/ylemontag/matlabio/ComplexMode.java new file mode 100644 index 0000000000000000000000000000000000000000..fa40464ff7f07cfe10353ded1f514bf8dc8ad827 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/ComplexMode.java @@ -0,0 +1,52 @@ +package plugins.ylemontag.matlabio; + +/** + * + * @author Yoann Le Montagner + * + * Import option for complex valued Matlab arrays + */ +public enum ComplexMode +{ + REAL_PART ("Real" ), + IMAGINARY_PART("Imaginary"), + BOTH ("Both" ); + + private String _description; + + /** + * Constructor + */ + private ComplexMode(String description) + { + _description = description; + } + + /** + * Description + */ + public String getDescription() + { + return _description; + } + + @Override + public String toString() + { + return getDescription(); + } + + /** + * Return the complex mode corresponding to the given description, or null + * if no mode corresponds + */ + public static ComplexMode fromDescription(String description) + { + for(ComplexMode c : values()) { + if(c.getDescription().equals(description)) { + return c; + } + } + return null; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/Cursor.java b/src/main/java/plugins/ylemontag/matlabio/Cursor.java new file mode 100644 index 0000000000000000000000000000000000000000..cf360866828d8df13473f8d39c195c9d5440f755 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/Cursor.java @@ -0,0 +1,414 @@ +package plugins.ylemontag.matlabio; + +import icy.sequence.Sequence; + +import java.io.IOException; + +import plugins.ylemontag.matlabio.DimensionMapping; +import plugins.ylemontag.matlabio.IcyDimension; + +/** + * + * @author Yoann Le Montagner + * + * Object used to browse a sequence, so that the pixels are visited in the + * order corresponding their logical arrangement in the Matlab array + */ +public abstract class Cursor +{ + /** + * Parameter defining which channels should be filled in the targeted sequence + */ + public enum ModeC + { + EVEN_C, + ODD_C , + ALL_C ; + } + + private int _sizeX; + private int _sizeY; + private int _sizeZ; + private int _sizeT; + private int _sizeC; + private DimensionManager[] _mapping; + private boolean _isValidPos; + protected int _posX; + protected int _posY; + protected int _posZ; + protected int _posT; + protected int _posC; + protected int _posXY; + protected Sequence _seq; + + @Override + public String toString() + { + return "(x,y,z,t,c)=(" + getX() + "," + getY() + "," + getZ() + "," + getT() + "," + getC() + ")"; + } + + /** + * Constructor + */ + protected Cursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + _sizeX = seq.getSizeX(); + _sizeY = seq.getSizeY(); + _sizeZ = seq.getSizeZ(); + _sizeT = seq.getSizeT(); + _sizeC = seq.getSizeC(); + IcyDimension[] inverseMapping = mapping.getInverseMapping(); + _mapping = new DimensionManager[5]; + for(int k=0; k<inverseMapping.length; ++k) { + switch(inverseMapping[k]) { + case X: _mapping[k] = new DimensionManagerX(k); break; + case Y: _mapping[k] = new DimensionManagerY(k); break; + case Z: _mapping[k] = new DimensionManagerZ(k); break; + case T: _mapping[k] = new DimensionManagerT(k); break; + case C: _mapping[k] = new DimensionManagerC(k, modeC==ModeC.ALL_C ? 1 : 2); break; + } + } + _posX = 0; + _posY = 0; + _posZ = 0; + _posT = 0; + _posC = modeC==ModeC.ODD_C ? 1 : 0; + _posXY = 0; + _seq = seq; + _isValidPos = + (_posX<_sizeX) && + (_posY<_sizeY) && + (_posZ<_sizeZ) && + (_posT<_sizeT) && + (_posC<_sizeC); + } + + /** + * Go to the next pixel + */ + public void next() + { + _mapping[0].goNext(); + } + + /** + * Execute the copy action at the current position + */ + public abstract void consume() throws IOException; + + /** + * Return true if the cursor points to a pixel in the sequence + */ + public boolean isValidPosition() + { + return _isValidPos; + } + + /** + * Size of the sequence in the X dimension + */ + public int getSizeX() + { + return _sizeX; + } + + /** + * Size of the sequence in the Y dimension + */ + public int getSizeY() + { + return _sizeY; + } + + /** + * Size of the sequence in the Z dimension + */ + public int getSizeZ() + { + return _sizeZ; + } + + /** + * Size of the sequence in the T dimension + */ + public int getSizeT() + { + return _sizeT; + } + + /** + * Size of the sequence in the C dimension + */ + public int getSizeC() + { + return _sizeC; + } + + /** + * Position X of the cursor + */ + public int getX() + { + return _posX; + } + + /** + * Position Y of the cursor + */ + public int getY() + { + return _posY; + } + + /** + * Position Z of the cursor + */ + public int getZ() + { + return _posZ; + } + + /** + * Position T of the cursor + */ + public int getT() + { + return _posT; + } + + /** + * Position C of the cursor + */ + public int getC() + { + return _posC; + } + + /** + * Refresh the pointers after a change of the Z coordinate + */ + protected abstract void notifyPosZChanged(); + + /** + * Refresh the pointers after a change of the T coordinate + */ + protected abstract void notifyPosTChanged(); + + /** + * Refresh the pointers after a change of the C coordinate + */ + protected abstract void notifyPosCChanged(); + + /** + * Functor for increasing dimensions + */ + private abstract class DimensionManager + { + private int _nextManager; + private int _size; + + /** + * Constructor + */ + protected DimensionManager(int order, int size) + { + _nextManager = order+1; + _size = size; + } + + /** + * Go to the next pixel + */ + public void goNext() + { + int newValue = getNextValue(); + if(newValue>=_size) { + resetValue(); + if(_nextManager>=5) { + _isValidPos = false; + return; + } + else { + _mapping[_nextManager].goNext(); + } + } + else { + increaseValue(); + } + } + + /** + * Increase the value of the managed coordinate + */ + protected abstract void increaseValue(); + + /** + * Set the value of the managed coordinate to 0 + */ + protected abstract void resetValue(); + + /** + * Return the current value of the managed coordinate + */ + protected abstract int getNextValue(); + } + + /** + * Manager for the X dimension + */ + private class DimensionManagerX extends DimensionManager + { + public DimensionManagerX(int order) + { + super(order, _sizeX); + } + + @Override + protected void increaseValue() + { + _posXY++; + _posX++; + } + + @Override + protected void resetValue() + { + _posXY -= _posX; + _posX = 0; + } + + @Override + protected int getNextValue() + { + return _posX+1; + } + } + + /** + * Manager for the Y dimension + */ + private class DimensionManagerY extends DimensionManager + { + public DimensionManagerY(int order) + { + super(order, _sizeY); + } + + @Override + protected void increaseValue() + { + _posXY += _sizeX; + _posY++; + } + + @Override + protected void resetValue() + { + _posXY = _posX; + _posY = 0; + } + + @Override + protected int getNextValue() + { + return _posY+1; + } + } + + /** + * Manager for the Z dimension + */ + private class DimensionManagerZ extends DimensionManager + { + public DimensionManagerZ(int order) + { + super(order, _sizeZ); + } + + @Override + protected void increaseValue() + { + _posZ++; + notifyPosZChanged(); + } + + @Override + protected void resetValue() + { + _posZ = 0; + notifyPosZChanged(); + } + + @Override + protected int getNextValue() + { + return _posZ+1; + } + } + + /** + * Manager for the T dimension + */ + private class DimensionManagerT extends DimensionManager + { + public DimensionManagerT(int order) + { + super(order, _sizeT); + } + + @Override + protected void increaseValue() + { + _posT++; + notifyPosTChanged(); + } + + @Override + protected void resetValue() + { + _posT = 0; + notifyPosTChanged(); + } + + @Override + protected int getNextValue() + { + return _posT+1; + } + } + + /** + * Manager for the C dimension + */ + private class DimensionManagerC extends DimensionManager + { + private int _step; + + public DimensionManagerC(int order, int step) + { + super(order, _sizeC); + _step = step; + } + + @Override + protected void increaseValue() + { + _posC += _step; + notifyPosCChanged(); + } + + @Override + protected void resetValue() + { + _posC = 0; + notifyPosCChanged(); + } + + @Override + protected int getNextValue() + { + return _posC+_step; + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/Cursors.java b/src/main/java/plugins/ylemontag/matlabio/Cursors.java new file mode 100644 index 0000000000000000000000000000000000000000..1bfe875c91c6932e1249ae86536f5511c0bfa7ae --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/Cursors.java @@ -0,0 +1,629 @@ +package plugins.ylemontag.matlabio; + +import icy.sequence.Sequence; + +import java.io.IOException; + +import plugins.ylemontag.matlabio.Cursor.ModeC; +import plugins.ylemontag.matlabio.lib.MLIOException; +import plugins.ylemontag.matlabio.lib.MLIStream; +import plugins.ylemontag.matlabio.lib.MLIStreams; +import plugins.ylemontag.matlabio.lib.MLOStream; +import plugins.ylemontag.matlabio.lib.MLOStreams; + +/** + * + * @author Yoann Le Montagner + * + * Specialized cursors supporting different types of sequences + */ +public class Cursors +{ + /** + * Factory function for sequence-writing cursors + */ + static public Cursor create(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStream data) + throws MLIOException + { + switch(data.getType()) { + case LOGICAL: return new LogicalCursorW (seq, mapping, modeC, (MLIStreams.Logical)data); + case DOUBLE : return new DoubleCursorW (seq, mapping, modeC, (MLIStreams.Double )data); + case SINGLE : return new FloatCursorW (seq, mapping, modeC, (MLIStreams.Single )data); + case INT8 : return new SignedByteCursorW (seq, mapping, modeC, (MLIStreams.Int8 )data); + case UINT8 : return new UnsignedByteCursorW (seq, mapping, modeC, (MLIStreams.UInt8 )data); + case INT16 : return new SignedShortCursorW (seq, mapping, modeC, (MLIStreams.Int16 )data); + case UINT16 : return new UnsignedShortCursorW(seq, mapping, modeC, (MLIStreams.UInt16 )data); + case INT32 : return new SignedIntCursorW (seq, mapping, modeC, (MLIStreams.Int32 )data); + case UINT32 : return new UnsignedIntCursorW (seq, mapping, modeC, (MLIStreams.UInt32 )data); + default: + throw new MLIOException("Unsupported Matlab type: " + data.getType()); + } + } + + /** + * Factory function for sequence-reading cursors + */ + static public Cursor create(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStream data) + throws MLIOException + { + switch(data.getType()) { + case DOUBLE: return new DoubleCursorR (seq, mapping, modeC, (MLOStreams.Double)data); + case SINGLE: return new FloatCursorR (seq, mapping, modeC, (MLOStreams.Single)data); + case INT8 : return new SignedByteCursorR (seq, mapping, modeC, (MLOStreams.Int8 )data); + case UINT8 : return new UnsignedByteCursorR (seq, mapping, modeC, (MLOStreams.UInt8 )data); + case INT16 : return new SignedShortCursorR (seq, mapping, modeC, (MLOStreams.Int16 )data); + case UINT16: return new UnsignedShortCursorR(seq, mapping, modeC, (MLOStreams.UInt16)data); + case INT32 : return new SignedIntCursorR (seq, mapping, modeC, (MLOStreams.Int32 )data); + case UINT32: return new UnsignedIntCursorR (seq, mapping, modeC, (MLOStreams.UInt32)data); + default: + throw new MLIOException("Unsupported Matlab type: " + data.getType()); + } + } + + + + //////////////////////////////////////////////////////////////////////////// + // Base-class cursors + + /** + * Cursor for double-typed sequences + */ + private static abstract class DoubleCursor extends Cursor + { + private double[][][][] _pointerTZCXY; + private double[][][] _pointerZCXY; + private double[][] _pointerCXY; + protected double[] _pointerXY; + + public DoubleCursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _pointerTZCXY = _seq.getDataXYCZTAsDouble(); + notifyPosTChanged(); + } + + @Override + protected void notifyPosZChanged() + { + _pointerCXY = _pointerZCXY[_posZ]; + notifyPosCChanged(); + } + + @Override + protected void notifyPosTChanged() + { + _pointerZCXY = _pointerTZCXY[_posT]; + notifyPosZChanged(); + } + + @Override + protected void notifyPosCChanged() + { + _pointerXY = _pointerCXY[_posC]; + } + } + + /** + * Cursor for float-typed sequences + */ + private static abstract class FloatCursor extends Cursor + { + private float[][][][] _pointerTZCXY; + private float[][][] _pointerZCXY; + private float[][] _pointerCXY; + protected float[] _pointerXY; + + public FloatCursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _pointerTZCXY = _seq.getDataXYCZTAsFloat(); + notifyPosTChanged(); + } + + @Override + protected void notifyPosZChanged() + { + _pointerCXY = _pointerZCXY[_posZ]; + notifyPosCChanged(); + } + + @Override + protected void notifyPosTChanged() + { + _pointerZCXY = _pointerTZCXY[_posT]; + notifyPosZChanged(); + } + + @Override + protected void notifyPosCChanged() + { + _pointerXY = _pointerCXY[_posC]; + } + } + + /** + * Cursor for byte-typed sequences + */ + private static abstract class ByteCursor extends Cursor + { + private byte[][][][] _pointerTZCXY; + private byte[][][] _pointerZCXY; + private byte[][] _pointerCXY; + protected byte[] _pointerXY; + + public ByteCursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _pointerTZCXY = _seq.getDataXYCZTAsByte(); + notifyPosTChanged(); + } + + @Override + protected void notifyPosZChanged() + { + _pointerCXY = _pointerZCXY[_posZ]; + notifyPosCChanged(); + } + + @Override + protected void notifyPosTChanged() + { + _pointerZCXY = _pointerTZCXY[_posT]; + notifyPosZChanged(); + } + + @Override + protected void notifyPosCChanged() + { + _pointerXY = _pointerCXY[_posC]; + } + } + + /** + * Cursor for short-typed sequences + */ + private static abstract class ShortCursor extends Cursor + { + private short[][][][] _pointerTZCXY; + private short[][][] _pointerZCXY; + private short[][] _pointerCXY; + protected short[] _pointerXY; + + public ShortCursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _pointerTZCXY = _seq.getDataXYCZTAsShort(); + notifyPosTChanged(); + } + + @Override + protected void notifyPosZChanged() + { + _pointerCXY = _pointerZCXY[_posZ]; + notifyPosCChanged(); + } + + @Override + protected void notifyPosTChanged() + { + _pointerZCXY = _pointerTZCXY[_posT]; + notifyPosZChanged(); + } + + @Override + protected void notifyPosCChanged() + { + _pointerXY = _pointerCXY[_posC]; + } + } + + /** + * Cursor for int-typed sequences + */ + private static abstract class IntCursor extends Cursor + { + private int[][][][] _pointerTZCXY; + private int[][][] _pointerZCXY; + private int[][] _pointerCXY; + protected int[] _pointerXY; + + public IntCursor(Sequence seq, DimensionMapping mapping, ModeC modeC) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _pointerTZCXY = _seq.getDataXYCZTAsInt(); + notifyPosTChanged(); + } + + @Override + protected void notifyPosZChanged() + { + _pointerCXY = _pointerZCXY[_posZ]; + notifyPosCChanged(); + } + + @Override + protected void notifyPosTChanged() + { + _pointerZCXY = _pointerTZCXY[_posT]; + notifyPosZChanged(); + } + + @Override + protected void notifyPosCChanged() + { + _pointerXY = _pointerCXY[_posC]; + } + } + + + + ////////////////////////////////////////////////////////////////////////////// + // Sequence-writing cursors + + /** + * Write in a double-valued sequence + */ + private static class DoubleCursorW extends DoubleCursor + { + private MLIStreams.Double _stream; + + public DoubleCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Double data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in a float-valued sequence + */ + private static class FloatCursorW extends FloatCursor + { + private MLIStreams.Single _stream; + + public FloatCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Single data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in a signed byte-valued sequence + */ + private static class SignedByteCursorW extends ByteCursor + { + private MLIStreams.Int8 _stream; + + public SignedByteCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Int8 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in an unsigned byte-valued sequence + */ + private static class UnsignedByteCursorW extends ByteCursor + { + private MLIStreams.UInt8 _stream; + + public UnsignedByteCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.UInt8 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in an unsigned byte-valued sequence from boolean-valued data + */ + private static class LogicalCursorW extends ByteCursor + { + private MLIStreams.Logical _stream; + + public LogicalCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Logical data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get() ? (byte)1 : (byte)0; + } + } + + /** + * Write in a signed short-valued sequence + */ + private static class SignedShortCursorW extends ShortCursor + { + private MLIStreams.Int16 _stream; + + public SignedShortCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Int16 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in an unsigned short-valued sequence + */ + private static class UnsignedShortCursorW extends ShortCursor + { + private MLIStreams.UInt16 _stream; + + public UnsignedShortCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.UInt16 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in a signed int-valued sequence + */ + private static class SignedIntCursorW extends IntCursor + { + private MLIStreams.Int32 _stream; + + public SignedIntCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.Int32 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + /** + * Write in an unsigned int-valued sequence + */ + private static class UnsignedIntCursorW extends IntCursor + { + private MLIStreams.UInt32 _stream; + + public UnsignedIntCursorW(Sequence seq, DimensionMapping mapping, ModeC modeC, MLIStreams.UInt32 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _pointerXY[_posXY] = _stream.get(); + } + } + + + + ////////////////////////////////////////////////////////////////////////////// + // Sequence-reading cursors + + /** + * Write in a double-valued sequence + */ + private static class DoubleCursorR extends DoubleCursor + { + private MLOStreams.Double _stream; + + public DoubleCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.Double data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in a float-valued sequence + */ + private static class FloatCursorR extends FloatCursor + { + private MLOStreams.Single _stream; + + public FloatCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.Single data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in a signed byte-valued sequence + */ + private static class SignedByteCursorR extends ByteCursor + { + private MLOStreams.Int8 _stream; + + public SignedByteCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.Int8 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in an unsigned byte-valued sequence + */ + private static class UnsignedByteCursorR extends ByteCursor + { + private MLOStreams.UInt8 _stream; + + public UnsignedByteCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.UInt8 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in a signed short-valued sequence + */ + private static class SignedShortCursorR extends ShortCursor + { + private MLOStreams.Int16 _stream; + + public SignedShortCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.Int16 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in an unsigned short-valued sequence + */ + private static class UnsignedShortCursorR extends ShortCursor + { + private MLOStreams.UInt16 _stream; + + public UnsignedShortCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.UInt16 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in a signed int-valued sequence + */ + private static class SignedIntCursorR extends IntCursor + { + private MLOStreams.Int32 _stream; + + public SignedIntCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.Int32 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } + + /** + * Read in an unsigned int-valued sequence + */ + private static class UnsignedIntCursorR extends IntCursor + { + private MLOStreams.UInt32 _stream; + + public UnsignedIntCursorR(Sequence seq, DimensionMapping mapping, ModeC modeC, MLOStreams.UInt32 data) + throws DimensionMapping.BadMapping + { + super(seq, mapping, modeC); + _stream = data; + } + + @Override + public void consume() throws IOException + { + _stream.put(_pointerXY[_posXY]); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/DimensionMapping.java b/src/main/java/plugins/ylemontag/matlabio/DimensionMapping.java new file mode 100644 index 0000000000000000000000000000000000000000..fcc7e4c4ab980b03bb7bfe489e093e3912ce2e2c --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/DimensionMapping.java @@ -0,0 +1,216 @@ +package plugins.ylemontag.matlabio; + +import plugins.ylemontag.matlabio.lib.MLIOException; + + +/** + * + * @author Yoann Le Montagner + * + * Define how to interpret the dimensions of a Matlab array + */ +public class DimensionMapping implements Cloneable +{ + /** + * Exception thrown by the getInverseMapping function + */ + public class BadMapping extends MLIOException + { + private static final long serialVersionUID = 1L; + + /** + * Constructor + */ + public BadMapping() + { + super("Invalid dimension mapping"); + } + } + + // Ex: _mapping[1] = 3; to map the 4th Matlab dimension to Y + private int[] _mapping; + + /** + * Constructor + * + * Default behavior: + * Y <-> Matlab dimension 1 + * X <-> Matlab dimension 2 + * Z <-> Matlab dimension 3 + * T <-> Matlab dimension 4 + * C <-> Matlab dimension 5 + */ + public DimensionMapping() + { + _mapping = new int[5]; + setDimensionY(0); + setDimensionX(1); + setDimensionZ(2); + setDimensionT(3); + setDimensionC(4); + } + + @Override + public DimensionMapping clone() + { + DimensionMapping retVal = new DimensionMapping(); + for(int k=0; k<5; ++k) { + retVal._mapping[k] = _mapping[k]; + } + return retVal; + } + + @Override + public String toString() + { + try { + IcyDimension[] inverseMap = getInverseMapping(); + String retVal = ""; + for(int k=0; k<inverseMap.length; ++k) { + if(k!=0) { + retVal += "\u00d7"; + } + retVal += inverseMap[k].toString(); + } + return retVal; + } + catch(BadMapping err) { + return "Invalid dimension mapping"; + } + } + + /** + * Check if the dimension mapping is valid, i.e. if _mapping actually + * represents a permutation of {0, 1, ..., 4} + */ + public boolean isValidMapping() + { + try { + ensureValidMapping(); + return true; + } + catch(BadMapping err) { + return false; + } + } + + /** + * Throw an exception if the given mapping is not valid + */ + public void ensureValidMapping() throws BadMapping + { + getInverseMapping(); + } + + /** + * Set the Matlab dimension corresponding to X + */ + public void setDimensionX(int src) + { + setDimension(IcyDimension.X, src); + } + + /** + * Set the Matlab dimension corresponding to Y + */ + public void setDimensionY(int src) + { + setDimension(IcyDimension.Y, src); + } + + /** + * Set the Matlab dimension corresponding to Z + */ + public void setDimensionZ(int src) + { + setDimension(IcyDimension.Z, src); + } + + /** + * Set the Matlab dimension corresponding to T + */ + public void setDimensionT(int src) + { + setDimension(IcyDimension.T, src); + } + + /** + * Set the Matlab dimension corresponding to C + */ + public void setDimensionC(int src) + { + setDimension(IcyDimension.C, src); + } + + /** + * Set a Matlab dimension + */ + public void setDimension(IcyDimension dim, int src) + { + _mapping[dim.getCode()] = src; + } + + /** + * Dimension corresponding to X + */ + public int getDimensionX() + { + return getDimension(IcyDimension.X); + } + + /** + * Dimension corresponding to Y + */ + public int getDimensionY() + { + return getDimension(IcyDimension.Y); + } + + /** + * Dimension corresponding to Z + */ + public int getDimensionZ() + { + return getDimension(IcyDimension.Z); + } + + /** + * Dimension corresponding to T + */ + public int getDimensionT() + { + return getDimension(IcyDimension.T); + } + + /** + * Dimension corresponding to C + */ + public int getDimensionC() + { + return getDimension(IcyDimension.C); + } + + /** + * Value of the given dimension + */ + public int getDimension(IcyDimension dim) + { + return _mapping[dim.getCode()]; + } + + /** + * Build the inverse mapping + */ + public IcyDimension[] getInverseMapping() throws BadMapping + { + IcyDimension[] retVal = new IcyDimension[5]; + for(IcyDimension dim : IcyDimension.values()) { + int current_dim = _mapping[dim.getCode()]; + if(current_dim<0 || current_dim>=5 || retVal[current_dim]!=null) { + throw new BadMapping(); + } + retVal[current_dim] = dim; + } + return retVal; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/IcyDimension.java b/src/main/java/plugins/ylemontag/matlabio/IcyDimension.java new file mode 100644 index 0000000000000000000000000000000000000000..749456cfedbd027750a285ce2cf1d081d03fb7ea --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/IcyDimension.java @@ -0,0 +1,42 @@ +package plugins.ylemontag.matlabio; + +/** + * + * @author Yoann Le Montagner + * + * The 5 dimensions defined by the ICY API + */ +public enum IcyDimension +{ + X("X", 0), + Y("Y", 1), + Z("Z", 2), + T("T", 3), + C("C", 4); + + private String _text; + private int _code; + + /** + * Constructor + */ + private IcyDimension(String text, int code) + { + _text = text; + _code = code; + } + + /** + * Integer code + */ + public int getCode() + { + return _code; + } + + @Override + public String toString() + { + return _text; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/MatlabExporter.java b/src/main/java/plugins/ylemontag/matlabio/MatlabExporter.java new file mode 100644 index 0000000000000000000000000000000000000000..409c68cb6b14d379a0f572b621a3f7c08c09c2bb --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/MatlabExporter.java @@ -0,0 +1,492 @@ +package plugins.ylemontag.matlabio; + +import icy.sequence.Sequence; +import icy.type.DataType; + +import java.io.File; +import java.io.IOException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import plugins.ylemontag.matlabio.lib.Controller; +import plugins.ylemontag.matlabio.lib.MLIOException; +import plugins.ylemontag.matlabio.lib.MLMeta; +import plugins.ylemontag.matlabio.lib.MLOStream; +import plugins.ylemontag.matlabio.lib.MLType; +import plugins.ylemontag.matlabio.lib.MatFileWriter; + +/** + * + * @author Yoann Le Montagner + * + * This class is used to create a Matlab .mat and to save some data in it + */ +public class MatlabExporter +{ + private MatFileWriter _writer; + private Lock _lock; + private Map<String, Item> _items; + + /** + * Constructor + * @param path Path of the .mat file + */ + public MatlabExporter(String path, boolean append) throws IOException + { + this(new MatFileWriter(path, append)); + } + + /** + * Constructor + * @param file Object pointing to a .mat file + */ + public MatlabExporter(File file, boolean append) throws IOException + { + this(new MatFileWriter(file, append)); + } + + /** + * Constructor + * @param reader Low-level .mat file reader + */ + public MatlabExporter(MatFileWriter writer) throws MLIOException + { + if(writer==null) { + throw new MLIOException( + "Trying to construct a MatlabExporter object from a null MatFileWriter object" + ); + } + _writer = writer; + _lock = new ReentrantLock(); + _items = new HashMap<String, Item>(); + } + + /** + * Return the list of all variable (both pending and already stored) + */ + public Set<String> getAllVariables() + { + Set<String> retVal = new HashSet<String>(); + retVal.addAll(getStoredVariables ()); + retVal.addAll(getPendingVariables()); + return retVal; + } + + /** + * Return the list of variable names already stored in the given file + */ + public Set<String> getStoredVariables() + { + return _writer.getKeys(); + } + + /** + * Return the list of variable names to be stored in the given file + */ + public Set<String> getPendingVariables() + { + return _items.keySet(); + } + + /** + * Check whether the given name corresponds to a stored variable + */ + public boolean isStoredVariable(String name) + { + return getStoredVariables().contains(name); + } + + /** + * Check whether the given name corresponds to a pending variable + */ + public boolean isPendingVariable(String name) + { + return getPendingVariables().contains(name); + } + + /** + * Return the meta informations associated to a given Matlab variable + * @param name Name of the variable in the Matlab .mat file + */ + public MLMeta getMeta(String name) + { + try { + return retrieveItem(name).getMeta(); + } + catch(MLIOException err) { + return _writer.getMeta(name); + } + } + + /** + * Mark the given sequence to be saved in the current file + * @param seq Sequence to export + * @param name Name of the variable to give to the sequence in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + */ + public void putData(Sequence seq, String name, DimensionMapping mapping) + throws MLIOException + { + putData(seq, name, mapping, false); + } + + /** + * Mark the given sequence to be saved in the current file + * @param seq Sequence to export + * @param name Name of the variable to give to the sequence in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param isComplex Whether the given sequence is complex-valued + */ + public void putData(final Sequence seq, final String name, final DimensionMapping mapping, final boolean isComplex) + throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + Item item = new Item(seq, name, mapping, isComplex); + _items.put(item.getMeta().getName(), item); + } + }); + } + + /** + * Copy the pending data contained in 'exporter' to the current object + */ + public void putPendingData(final MatlabExporter exporter) throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + for(Item item : exporter._items.values()) { + String key = item._meta.getName(); + Item localItem = new Item(item._data, key, item._mapping, item._meta.getIsComplex()); + _items.put(key, localItem); + } + } + }); + } + + /** + * Change the name of a variable + */ + public void updateName(final String oldName, final String newName) throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + Item item = retrieveItem(oldName); + item.setName(newName); + _items.remove(oldName); + _items.put(item.getMeta().getName(), item); + } + }); + } + + /** + * Change the dimension mapping affected to a variable + */ + public void updateDimensionMapping(final String name, final DimensionMapping mapping) throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + Item item = retrieveItem(name); + item.setDimensionMapping(mapping); + } + }); + } + + /** + * Change the complex flag affected to a variable + */ + public void updateIsComplex(final String name, final boolean isComplex) throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + Item item = retrieveItem(name); + item.setIsComplex(isComplex); + } + }); + } + + /** + * Remove the variable with the given name from the list of variable to export + */ + public void eraseData(final String name) throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + _items.remove(name); + } + }); + } + + + /** + * Clear the list of variable to export + */ + public void eraseAll() throws MLIOException + { + tryLock(new WriteOperation() + { + @Override + public void run() throws MLIOException { + _items.clear(); + } + }); + } + + /** + * Write the data in the underlying file + */ + public void export() throws IOException + { + try { + export(new Controller()); + } + catch(Controller.CanceledByUser err) { + throw new RuntimeException("A Matlab file export operation have been unexpectedly interrupted"); + } + } + + /** + * Write the data in the underlying file + */ + public void export(Controller controller) throws IOException, Controller.CanceledByUser + { + _lock.lock(); + Set<String> toBeRemoved = new HashSet<String>(); + try { + controller.startCounter(_items.size()); + for(Item item : _items.values()) { + item.append(_writer, controller); + toBeRemoved.add(item.getMeta().getName()); + } + controller.stopCounter(); + } + finally { + for(String key : toBeRemoved) { + _items.remove(key); + } + _lock.unlock(); + } + } + + /** + * Retrieve the given item and throw an exception if it does not exist + */ + private Item retrieveItem(String name) throws MLIOException + { + Item item = _items.get(name); + if(item==null) { + throw new MLIOException("Cannot find variable named " + name); + } + return item; + } + + /** + * Execute a method while holding the lock (return immediately if already locked) + */ + private void tryLock(WriteOperation method) throws MLIOException + { + if(!_lock.tryLock()) { + throw new MLIOException( + "Cannot modify the MatlabExporter object while the previous export operation is not finished" + ); + } + try { + method.run(); + } + finally { + _lock.unlock(); + } + } + + /** + * Compute the dimension of the matlab object corresponding to the given sequence + * and dimension mapping scheme + */ + private static int[] computeSize(DimensionMapping mapping, boolean isComplex, Sequence data) + throws MLIOException + { + mapping.ensureValidMapping(); + if(isComplex && (data.getSizeC()%2 != 0)) { + throw new MLIOException("Complex sequences must have an even number of channels"); + } + int[] rawRetVal = new int[5]; + rawRetVal[mapping.getDimensionX()] = data.getSizeX(); + rawRetVal[mapping.getDimensionY()] = data.getSizeY(); + rawRetVal[mapping.getDimensionZ()] = data.getSizeZ(); + rawRetVal[mapping.getDimensionT()] = data.getSizeT(); + rawRetVal[mapping.getDimensionC()] = isComplex ? data.getSizeC()/2 : data.getSizeC(); + int retValLength = 5; + for(int k=4; k>=2; --k) { + if(rawRetVal[k]==1) { + retValLength = k; + } + else { + break; + } + } + int[] retVal = new int[retValLength]; + for(int k=0; k<retValLength; ++k) { + retVal[k] = rawRetVal[k]; + } + return retVal; + } + + /** + * Return the Matlab type suitable for storing a sequence having the given Icy datatype + */ + private static MLType computeType(DataType icyType) throws MLIOException + { + switch(icyType) { + case DOUBLE: return MLType.DOUBLE; + case FLOAT : return MLType.SINGLE; + case BYTE : return MLType.INT8 ; + case UBYTE : return MLType.UINT8 ; + case SHORT : return MLType.INT16 ; + case USHORT: return MLType.UINT16; + case INT : return MLType.INT32 ; + case UINT : return MLType.UINT32; + default: + throw new MLIOException("Unsupported Icy type: " + icyType); + } + } + + /** + * Remove all the forbidden characters in the given string so that it could + * be admissible as a Matlab variable name + */ + private static String computeName(String rawName) + { + String retVal = ""; + for(int k=0; k<rawName.length(); ++k) { + char c = rawName.charAt(k); + if((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9' && k>0)) + retVal += c; + else + retVal += '_'; + } + return retVal; + } + + /** + * Interface to implement for the tryLock method + */ + private interface WriteOperation + { + /** + * Method called by tryLock + */ + public void run() throws MLIOException; + } + + /** + * Item contained in the file + */ + private static class Item + { + private Sequence _data; + private MLMeta _meta; + private DimensionMapping _mapping; + + /** + * Constructor + */ + public Item(Sequence data, String name, DimensionMapping mapping, boolean isComplex) + throws MLIOException + { + _data = data; + refresh(name, mapping, isComplex); + } + + /** + * Meta-data associated to this item + */ + public MLMeta getMeta() + { + return _meta; + } + + /** + * Change the name + */ + public void setName(String newName) throws MLIOException + { + refresh(newName, _mapping, _meta.getIsComplex()); + } + + /** + * Change the dimension mapping + */ + public void setDimensionMapping(DimensionMapping newMapping) throws MLIOException + { + refresh(_meta.getName(), newMapping, _meta.getIsComplex()); + } + + /** + * Change the complex flag + */ + public void setIsComplex(boolean newIsComplex) throws MLIOException + { + refresh(_meta.getName(), _mapping, newIsComplex); + } + + /** + * Save the item at the end of the given file + */ + public void append(MatFileWriter file, Controller controller) + throws IOException, Controller.CanceledByUser + { + controller.checkPoint(); + MLOStream stream = file.putDataAsStream(_meta); + int nbSteps = _meta.getIsComplex() ? 2*_meta.getSize() : _meta.getSize(); + controller.startCounter(nbSteps); + if(_meta.getIsComplex()) { + feedStream(stream, Cursor.ModeC.EVEN_C, controller); + feedStream(stream, Cursor.ModeC.ODD_C , controller); + } + else { + feedStream(stream, Cursor.ModeC.ALL_C, controller); + } + controller.stopCounter(); + } + + /** + * Feed an output stream (one-pass) + */ + private void feedStream(MLOStream target, Cursor.ModeC modeC, Controller controller) + throws IOException, Controller.CanceledByUser + { + for(Cursor curs=Cursors.create(_data, _mapping, modeC, target); curs.isValidPosition(); curs.next()) { + controller.checkPointNext(); + curs.consume(); + } + } + + /** + * Create a MLMeta object for current sequence with the suitable name + */ + private void refresh(String rawName, DimensionMapping mapping, boolean isComplex) throws MLIOException + { + MLType type = computeType(_data.getDataType_()); + String name = computeName(rawName); + int[] dimensions = computeSize(mapping, isComplex, _data); + _meta = new MLMeta(type, name, dimensions, isComplex); + _mapping = mapping; + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/MatlabIOLibrary.java b/src/main/java/plugins/ylemontag/matlabio/MatlabIOLibrary.java new file mode 100644 index 0000000000000000000000000000000000000000..74b56282a1a17363d854efcde9baeeccd84a2446 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/MatlabIOLibrary.java @@ -0,0 +1,15 @@ +package plugins.ylemontag.matlabio; + +import icy.plugin.abstract_.Plugin; +import icy.plugin.interface_.PluginLibrary; + +/** + * + * @author Yoann Le Montagner + * + * Entry point for the library + */ +public class MatlabIOLibrary extends Plugin implements PluginLibrary +{ + // That's all! +} diff --git a/src/main/java/plugins/ylemontag/matlabio/MatlabImporter.java b/src/main/java/plugins/ylemontag/matlabio/MatlabImporter.java new file mode 100644 index 0000000000000000000000000000000000000000..70f393d13ec1dcd93ef466a180f85aed811c928f --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/MatlabImporter.java @@ -0,0 +1,337 @@ +package plugins.ylemontag.matlabio; + +import icy.image.IcyBufferedImage; +import icy.sequence.Sequence; +import icy.type.DataType; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import plugins.ylemontag.matlabio.lib.Controller; +import plugins.ylemontag.matlabio.lib.MLIOException; +import plugins.ylemontag.matlabio.lib.MLIStream; +import plugins.ylemontag.matlabio.lib.MLMeta; +import plugins.ylemontag.matlabio.lib.MLObject; +import plugins.ylemontag.matlabio.lib.MatFileReader; + +/** + * + * @author Yoann Le Montagner + * + * This class is used to browse a Matlab .mat and to import some part of + * its content as a sequence. + */ +public class MatlabImporter +{ + private MatFileReader _reader; + + /** + * Constructor + * @param path Path of the .mat file + */ + public MatlabImporter(String path) throws IOException + { + this(new MatFileReader(path)); + } + + /** + * Constructor + * @param file Object pointing to a .mat file + */ + public MatlabImporter(File file) throws IOException + { + this(new MatFileReader(file)); + } + + /** + * Constructor + * @param reader Low-level .mat file reader + */ + public MatlabImporter(MatFileReader reader) throws MLIOException + { + if(reader==null) { + throw new MLIOException( + "Trying to construct a MatlabImporter object from a null MatFileReader object" + ); + } + _reader = reader; + } + + /** + * Return the list of variable names stored in the .mat file + */ + public Set<String> getVariables() + { + return _reader.getKeys(); + } + + /** + * Return the list of importable sequences stored in the .mat file + * A variable can be imported if it is of type MLNumericArray and if it + * has at most 5 dimensions. + */ + public Set<String> getImportableSequences() + { + Set<String> retVal = new HashSet<String>(); + Set<String> keys = getVariables(); + for(String key : keys) { + MLMeta meta = _reader.getMeta(key); + if(isImportableAsSequence(meta)) { + retVal.add(key); + } + } + return retVal; + } + + /** + * Return the meta informations associated to a given Matlab variable + * @param name Name of the variable in the Matlab .mat file + */ + public MLMeta getMeta(String name) + { + return _reader.getMeta(name); + } + + /** + * Check whether a Matlab variable can be imported as a sequence or not + */ + public boolean isImportableAsSequence(String name) + { + MLMeta meta = getMeta(name); + return meta==null ? false : isImportableAsSequence(meta); + } + + /** + * Check whether a Matlab variable can be imported as a sequence or not + */ + public static boolean isImportableAsSequence(MLMeta meta) + { + if(meta.getDimensions().length>5) { + return false; + } + switch(meta.getType()) { + case LOGICAL: + case DOUBLE : + case SINGLE : + case INT8 : + case UINT8 : + case INT16 : + case UINT16 : + case INT32 : + case UINT32 : + return true; + default: + return false; + } + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + */ + public Sequence getSequence(String name, DimensionMapping mapping, ComplexMode complexMode) + throws IOException + { + try { + return getSequence(name, mapping, complexMode, new Controller()); + } + catch(Controller.CanceledByUser err) { + throw new RuntimeException("Unreachable code point"); + } + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + * @param outName The name that will be affected to the imported sequence + */ + public Sequence getSequence(String name, DimensionMapping mapping, ComplexMode complexMode, + String outName) throws IOException + { + try { + return getSequence(name, mapping, complexMode, outName, new Controller()); + } + catch(Controller.CanceledByUser err) { + throw new RuntimeException("Unreachable code point"); + } + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + * @param out Output sequence + */ + public void getSequence(String name, DimensionMapping mapping, ComplexMode complexMode, + Sequence out) throws IOException + { + try { + getSequence(name, mapping, complexMode, out, new Controller()); + } + catch(Controller.CanceledByUser err) { + throw new RuntimeException("Unreachable code point"); + } + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + * @param controller Manager for the working thread + */ + public Sequence getSequence(String name, DimensionMapping mapping, ComplexMode complexMode, + Controller controller) throws IOException, Controller.CanceledByUser + { + return getSequence(name, mapping, complexMode, name, controller); + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + * @param outName The name that will be affected to the imported sequence + * @param controller Manager for the working thread + */ + public Sequence getSequence(String name, DimensionMapping mapping, ComplexMode complexMode, + String outName, Controller controller) throws IOException, Controller.CanceledByUser + { + Sequence retVal = new Sequence(); + retVal.setName(outName); + getSequence(name, mapping, complexMode, retVal, controller); + return retVal; + } + + /** + * Import a variable as a new sequence + * @param name Name of the variable in the Matlab .mat file + * @param mapping Object used to describe how to interpret the dimensions of a Matlab object + * @param complexMode Define how complex-valued Matlab arrays should be imported + * @param out Output sequence + * @param controller Manager for the working thread + */ + public void getSequence(String name, DimensionMapping mapping, ComplexMode complexMode, + Sequence out, Controller controller) throws IOException, Controller.CanceledByUser + { + // Raw data + controller.checkPoint(); + MLIStream rawData = _reader.getDataAsStream(name); + controller.checkPoint(); + + // Size and datatype of the sequence + boolean isComplex = rawData.getIsComplex(); + int sizeX = computeSize(mapping, IcyDimension.X, rawData); + int sizeY = computeSize(mapping, IcyDimension.Y, rawData); + int sizeZ = computeSize(mapping, IcyDimension.Z, rawData); + int sizeT = computeSize(mapping, IcyDimension.T, rawData); + int sizeC = computeSize(mapping, IcyDimension.C, rawData); + int numElems = sizeX*sizeY*sizeZ*sizeT*sizeC; + if(complexMode==ComplexMode.BOTH && isComplex) { + sizeC *= 2; + } + DataType outType = computeType(rawData); + + // If necessary, allocate the output sequence + controller.checkPoint(); + if(outType!=out.getDataType_() + || out.getSizeX()!=sizeX + || out.getSizeY()!=sizeY + || out.getSizeZ()!=sizeZ + || out.getSizeT()!=sizeT + || out.getSizeC()!=sizeC + ) { + out.beginUpdate(); + try { + out.removeAllImages(); + for(int t=0; t<sizeT; ++t) { + for(int z=0; z<sizeZ; ++z) { + IcyBufferedImage currentFrame = new IcyBufferedImage(sizeX, sizeY, sizeC, outType); + out.setImage(t, z, currentFrame); + controller.checkPoint(); + } + } + } + finally { + out.endUpdate(); + } + } + + // Feed the sequence + controller.startCounter(isComplex && complexMode!=ComplexMode.REAL_PART ? 2*numElems : numElems); + if(isComplex) { + switch(complexMode) + { + case REAL_PART: + feedSequence(out, mapping, Cursor.ModeC.ALL_C, rawData, controller); + break; + + case IMAGINARY_PART: + rawData.skip(numElems, controller); + feedSequence(out, mapping, Cursor.ModeC.ALL_C, rawData, controller); + break; + + case BOTH: + feedSequence(out, mapping, Cursor.ModeC.EVEN_C, rawData, controller); + feedSequence(out, mapping, Cursor.ModeC.ODD_C , rawData, controller); + break; + } + } + else { + feedSequence(out, mapping, Cursor.ModeC.ALL_C, rawData, controller); + } + controller.stopCounter(); + out.dataChanged(); + } + + /** + * Feed a sequence (one-pass) + */ + private void feedSequence(Sequence seq, DimensionMapping mapping, Cursor.ModeC modeC, MLIStream rawData, + Controller controller) throws IOException, Controller.CanceledByUser + { + for(Cursor curs=Cursors.create(seq, mapping, modeC, rawData); curs.isValidPosition(); curs.next()) { + controller.checkPointNext(); + curs.consume(); + } + } + + /** + * Compute the dimension of the sequence to be created according to the + * current dimension mapping and the dimensions of the Matlab object + */ + private int computeSize(DimensionMapping mapping, IcyDimension icyDim, MLObject rawData) + { + int rawDim = mapping.getDimension(icyDim); + int[] rawSize = rawData.getDimensions(); + return rawDim<rawSize.length ? rawSize[rawDim] : 1; + } + + /** + * Return the correct sequence datatype according to the type of the data + * embedded in the Matlab object + */ + static private DataType computeType(MLObject rawData) throws MLIOException + { + switch(rawData.getType()) { + case LOGICAL: return DataType.UBYTE ; + case DOUBLE : return DataType.DOUBLE; + case SINGLE : return DataType.FLOAT ; + case INT8 : return DataType.BYTE ; + case UINT8 : return DataType.UBYTE ; + case INT16 : return DataType.SHORT ; + case UINT16 : return DataType.USHORT; + case INT32 : return DataType.INT ; + case UINT32 : return DataType.UINT ; + default: + throw new MLIOException("Unsupported Matlab type: " + rawData.getType()); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/ComplexModeComponent.java b/src/main/java/plugins/ylemontag/matlabio/gui/ComplexModeComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..974a0666af1e7a58da4ad6cf5e001845ab188278 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/ComplexModeComponent.java @@ -0,0 +1,52 @@ +package plugins.ylemontag.matlabio.gui; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; + +import plugins.ylemontag.matlabio.ComplexMode; + +/** + * + * @author Yoann Le Montagner + * + * Component letting the user choose one mode of import for complex objects + */ +public class ComplexModeComponent extends JComboBox { + + private static final long serialVersionUID = 1L; + + private DefaultComboBoxModel _model; + + /** + * Constructor + */ + public ComplexModeComponent() { + super(); + _model = new DefaultComboBoxModel(); + setModel(_model); + feedModel(); + } + + /** + * Selected mode + */ + public ComplexMode getComplexMode() { + return (ComplexMode)getSelectedItem(); + } + + /** + * Change the selected mode + */ + public void setComplexMode(ComplexMode src) { + setSelectedItem(src); + } + + /** + * Feed the model + */ + private void feedModel() { + for(ComplexMode m : ComplexMode.values()) { + _model.addElement(m); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/DimensionMappingComponent.java b/src/main/java/plugins/ylemontag/matlabio/gui/DimensionMappingComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..d13dd090444643a56f74ec8fe0d81364cb892f4a --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/DimensionMappingComponent.java @@ -0,0 +1,265 @@ +package plugins.ylemontag.matlabio.gui; + +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; + +import plugins.ylemontag.matlabio.DimensionMapping; +import plugins.ylemontag.matlabio.IcyDimension; + +/** + * + * @author Yoann Le Montagner + * + * Component used to configure a dimension mapping strategy + */ +public class DimensionMappingComponent extends JButton +{ + private static final long serialVersionUID = 1L; + + /** + * Interface to implement in order to listen to dimension mapping changes + */ + public interface DimensionMappingChangedListener + { + /** + * Action fired when a file change occur + */ + public void actionPerformed(); + } + + private DimensionMapping _mapping; + private LinkedList<DimensionMappingChangedListener> _listeners; + private boolean _shuntListeners; + + /** + * Constructor + */ + public DimensionMappingComponent() + { + super(); + _mapping = new DimensionMapping(); + refreshButtonLabel(); + _listeners = new LinkedList<DimensionMappingChangedListener>(); + _shuntListeners = false; + + // Listener + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { + onButtonClicked(); + } + }); + } + + /** + * Selected dimension mapping + */ + public DimensionMapping getDimensionMapping() + { + return _mapping.clone(); + } + + /** + * Dimension corresponding to X + */ + public int getDimensionX() + { + return _mapping.getDimensionX(); + } + + /** + * Dimension corresponding to Y + */ + public int getDimensionY() + { + return _mapping.getDimensionY(); + } + + /** + * Dimension corresponding to Z + */ + public int getDimensionZ() + { + return _mapping.getDimensionZ(); + } + + /** + * Dimension corresponding to T + */ + public int getDimensionT() + { + return _mapping.getDimensionT(); + } + + /** + * Dimension corresponding to C + */ + public int getDimensionC() + { + return _mapping.getDimensionC(); + } + + /** + * Set the dimension mapping + */ + public void setDimensions(int dimX, int dimY, int dimZ, int dimT, int dimC) + { + if( + dimX==_mapping.getDimensionX() && + dimY==_mapping.getDimensionY() && + dimZ==_mapping.getDimensionZ() && + dimT==_mapping.getDimensionT() && + dimC==_mapping.getDimensionC() + ) { + return; + } + _mapping.setDimensionX(dimX); + _mapping.setDimensionY(dimY); + _mapping.setDimensionZ(dimZ); + _mapping.setDimensionT(dimT); + _mapping.setDimensionC(dimC); + refreshButtonLabel(); + fireDimensionMappingChangedListeners(); + } + + /** + * Listen to the file change events + */ + public void addDimensionMappingChangedListener(DimensionMappingChangedListener l) + { + _listeners.add(l); + } + + /** + * Return true if the listeners are disabled + */ + public boolean getShuntListeners() + { + return _shuntListeners; + } + + /** + * Deactivate or re-activate the listeners + */ + public void setShuntListeners(boolean value) + { + _shuntListeners = value; + } + + /** + * Fire the listeners + */ + private void fireDimensionMappingChangedListeners() + { + if(_shuntListeners) { + return; + } + for(DimensionMappingChangedListener l : _listeners) { + l.actionPerformed(); + } + } + + /** + * Action performed when the user click on the button + */ + private void onButtonClicked() + { + DimensionMapping currentMapping = _mapping; + boolean canceled = false; + while(true) { + EditComponent chooser = new EditComponent(); + chooser.initMapping(currentMapping); + int retVal = JOptionPane.showOptionDialog(this, chooser, "Dimension mapping", + JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, null); + if(retVal==JOptionPane.OK_OPTION) { + currentMapping = chooser.retrieveMapping(); + if(currentMapping.isValidMapping()) { + break; + } + else { + JOptionPane.showMessageDialog(this, "Invalid dimension mapping", "Error", + JOptionPane.ERROR_MESSAGE); + } + } + else { + canceled = true; + break; + } + } + if(!canceled) { + setDimensions( + currentMapping.getDimensionX(), + currentMapping.getDimensionY(), + currentMapping.getDimensionZ(), + currentMapping.getDimensionT(), + currentMapping.getDimensionC() + ); + } + } + + /** + * Refresh the mapping description on the button label + */ + private void refreshButtonLabel() + { + setText(_mapping.toString()); + } + + /** + * Component used in the edit dialog + */ + private static class EditComponent extends JPanel + { + private static final long serialVersionUID = 1L; + + private IcyDimensionComponent[] _mapping; + + /** + * Constructor + */ + public EditComponent() + { + setLayout(new GridLayout(5, 2, 5, 5)); + _mapping = new IcyDimensionComponent[5]; + for(int k=0; k<5; ++k) { + _mapping[k] = new IcyDimensionComponent(); + add(new JLabel("Dimension " + (k+1))); + add(_mapping[k]); + } + } + + /** + * Initialize the component with an existing DimensionMapping object + */ + public void initMapping(DimensionMapping src) + { + for(IcyDimension dim : IcyDimension.values()) { + int v = src.getDimension(dim); + if(v>=0 && v<_mapping.length) { + _mapping[v].setIcyDimension(dim); + } + } + } + + /** + * Retrieve the customized DimensionMapping object + */ + public DimensionMapping retrieveMapping() + { + DimensionMapping retVal = new DimensionMapping(); + for(int k=0; k<_mapping.length; ++k) { + IcyDimension dim = _mapping[k].getIcyDimension(); + retVal.setDimension(dim, k); + } + return retVal; + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/FileChooserComponent.java b/src/main/java/plugins/ylemontag/matlabio/gui/FileChooserComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..667b50aefd853b17e61cacd0d5e8a138a4b1a88a --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/FileChooserComponent.java @@ -0,0 +1,147 @@ +package plugins.ylemontag.matlabio.gui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.io.File; +import java.util.LinkedList; + +import javax.swing.JButton; +import javax.swing.JFileChooser; +import javax.swing.filechooser.FileNameExtensionFilter; + +/** + * + * @author Yoann Le Montagner + * + * Component used to select a .mat file + */ +public class FileChooserComponent extends JButton +{ + private static final long serialVersionUID = 1L; + + /** + * Interface to implement in order to listen to file changes + */ + public interface FileChangedListener + { + /** + * Action fired when a file change occur + */ + public void actionPerformed(File newFile); + } + + /** + * File chooser dialog mode + */ + public static enum Mode + { + OPEN_DIALOG, + SAVE_DIALOG + } + + private Mode _mode; + private File _file; + private JFileChooser _fileChooser; + private LinkedList<FileChangedListener> _listeners; + + /** + * Constructor + */ + public FileChooserComponent(Mode mode, FileNameExtensionFilter filter) + { + super(); + refreshButtonLabel(); + _listeners = new LinkedList<FileChangedListener>(); + + // File chooser + _mode = mode; + _fileChooser = new JFileChooser(); + _fileChooser.setFileFilter(filter); + + // Listener + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { + onButtonClicked(); + } + }); + } + + /** + * Current directory + */ + public File getCurrentDirectory() + { + return _fileChooser.getCurrentDirectory(); + } + + /** + * Change the current directory + */ + public void setCurrentDirectory(File newDirectory) + { + _fileChooser.setCurrentDirectory(newDirectory); + } + + /** + * Selected file + */ + public File getSelectedFile() + { + return _file; + } + + /** + * Change the selected file + */ + public void setSelectedFile(File newFile) + { + if(_file==null && newFile==null) { + return; + } + else if(_file!=null && newFile!=null && _file.equals(newFile)) { + return; + } + _file = newFile; + refreshButtonLabel(); + for(FileChangedListener l : _listeners) { + l.actionPerformed(_file); + } + } + + /** + * Listen to the file change events + */ + public void addFileChangedListener(FileChangedListener l) + { + _listeners.add(l); + } + + /** + * Action performed when the user click on the button + */ + private void onButtonClicked() + { + if(_file!=null) { + _fileChooser.setSelectedFile(_file); + } + int retVal = 0; + switch(_mode) { + case OPEN_DIALOG: retVal = _fileChooser.showOpenDialog(this); break; + case SAVE_DIALOG: retVal = _fileChooser.showSaveDialog(this); break; + } + if(retVal==JFileChooser.APPROVE_OPTION) { + setSelectedFile(_fileChooser.getSelectedFile()); + } + } + + /** + * Refresh the file name on the button label + */ + private void refreshButtonLabel() + { + String newLabel = _file==null ? "Select a file..." : _file.getName(); + setText(newLabel); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/IcyDimensionComponent.java b/src/main/java/plugins/ylemontag/matlabio/gui/IcyDimensionComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..0de8f37b0400edc2fe24b4e250530fb947e7fd1a --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/IcyDimensionComponent.java @@ -0,0 +1,55 @@ +package plugins.ylemontag.matlabio.gui; + +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; + +import plugins.ylemontag.matlabio.IcyDimension; + +/** + * + * @author Yoann Le Montagner + * + * Component letting the user choose one dimension among the 5 dimensions + * defined by the ICY API + */ +public class IcyDimensionComponent extends JComboBox { + + private static final long serialVersionUID = 1L; + + private DefaultComboBoxModel _model; + + /** + * Constructor + */ + public IcyDimensionComponent() { + super(); + _model = new DefaultComboBoxModel(); + setModel(_model); + feedModel(); + } + + /** + * Selected dimension + */ + public IcyDimension getIcyDimension() { + return (IcyDimension)getSelectedItem(); + } + + /** + * Change the selected dimension + */ + public void setIcyDimension(IcyDimension src) { + setSelectedItem(src); + } + + /** + * Feed the model + */ + private void feedModel() { + _model.addElement(IcyDimension.X); + _model.addElement(IcyDimension.Y); + _model.addElement(IcyDimension.Z); + _model.addElement(IcyDimension.T); + _model.addElement(IcyDimension.C); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/MatlabProgressFrame.java b/src/main/java/plugins/ylemontag/matlabio/gui/MatlabProgressFrame.java new file mode 100644 index 0000000000000000000000000000000000000000..283ec22622ff7922900a99860ed12b2cf369b715 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/MatlabProgressFrame.java @@ -0,0 +1,46 @@ +package plugins.ylemontag.matlabio.gui; + +import icy.gui.frame.progress.CancelableProgressFrame; + +import java.awt.event.ActionEvent; +import java.util.Timer; +import java.util.TimerTask; + +import plugins.ylemontag.matlabio.lib.Controller; + +/** + * + * @author Yoann Le Montagner + * + * Progress frame for the Matlab I/O operations + */ +public class MatlabProgressFrame extends CancelableProgressFrame +{ + private Controller _controller; + private Timer _timer; + + /** + * Constructor + */ + public MatlabProgressFrame(String message, Controller controller) + { + super(message); + _controller = controller; + setLength(1.0); + _timer = new Timer(); + _timer.schedule(new TimerTask() + { + @Override + public void run() { + setPosition(_controller.getCurrentProgress()); + } + }, 0, 100); + } + + @Override + public void actionPerformed(ActionEvent e) + { + _controller.cancelComputation(); + setMessage("Submitting cancel request..."); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/gui/SequenceButton.java b/src/main/java/plugins/ylemontag/matlabio/gui/SequenceButton.java new file mode 100644 index 0000000000000000000000000000000000000000..1f8c73fa3fa820ff81a7f686d363a0ec861ace4e --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/gui/SequenceButton.java @@ -0,0 +1,151 @@ +package plugins.ylemontag.matlabio.gui; + +import icy.gui.main.MainAdapter; +import icy.gui.main.MainEvent; +import icy.main.Icy; +import icy.sequence.Sequence; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.LinkedList; + +import javax.swing.JButton; +import javax.swing.JMenuItem; +import javax.swing.JPopupMenu; + +/** + * + * @author Yoann Le Montagner + * + * Select an opened sequence by clicking on a button + */ +public class SequenceButton extends JButton +{ + private static final long serialVersionUID = 1L; + + /** + * Listener to implement to watch at events forwarded by this component + */ + public interface SequenceButtonListener + { + /** + * Triggered method + * @param seq Selected sequence + */ + public void eventTriggered(Sequence seq); + } + + /** + * Enabling/disabling mode for the component + */ + public enum Mode + { + ALWAYS_ENABLED , + AUTOMATIC , + ALWAYS_DISABLED + } + + private JPopupMenu _popupMenu; + private LinkedList<SequenceButtonListener> _listeners; + private Mode _mode; + + /** + * Constructor + */ + public SequenceButton(String label) + { + super(label); + _mode = Mode.AUTOMATIC; + _listeners = new LinkedList<SequenceButtonListener>(); + _popupMenu = new JPopupMenu(); + addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { + showPopup(); + } + }); + Icy.getMainInterface().addListener(new MainAdapter() + { + @Override + public void sequenceOpened(MainEvent event) { + refreshSensitivity(); + } + + @Override + public void sequenceClosed(MainEvent event) { + refreshSensitivity(); + } + }); + refreshSensitivity(); + } + + /** + * Current enabling/disabling mode + */ + public Mode getMode() + { + return _mode; + } + + /** + * Change the current enabling/disabling mode + */ + public void setMode(Mode newMode) + { + _mode = newMode; + refreshSensitivity(); + } + + /** + * Add a new listener + */ + public void addSequenceButtonListener(SequenceButtonListener l) + { + _listeners.add(l); + } + + /** + * Display the popup menu + */ + private void showPopup() + { + _popupMenu.removeAll(); + for(final Sequence seq : Icy.getMainInterface().getSequences()) { + JMenuItem menuItem = new JMenuItem(seq.getName()); + menuItem.addActionListener(new ActionListener() + { + @Override + public void actionPerformed(ActionEvent e) { + fireClickOnMenuItem(seq); + } + }); + _popupMenu.add(menuItem); + } + _popupMenu.show(this, 0, getHeight()); + } + + /** + * Refresh the enable state of the button + */ + private void refreshSensitivity() + { + boolean enable = false; + switch(_mode) { + case ALWAYS_ENABLED : enable = true; break; + case AUTOMATIC : enable = Icy.getMainInterface().getSequences().size()>0; break; + case ALWAYS_DISABLED: enable = false; break; + } + setEnabled(enable); + } + + /** + * Respond to a click on a menu item + */ + private void fireClickOnMenuItem(Sequence seq) + { + for(SequenceButtonListener l : _listeners) { + l.eventTriggered(seq); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/Controller.java b/src/main/java/plugins/ylemontag/matlabio/lib/Controller.java new file mode 100644 index 0000000000000000000000000000000000000000..e797b68af2522c2b76d22b648a695ce57054d198 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/Controller.java @@ -0,0 +1,135 @@ +package plugins.ylemontag.matlabio.lib; + +import java.util.LinkedList; + + +/** + * + * @author Yoann Le Montagner + * + * Object used to control and interrupt a potentially long operation on a Matlab .mat file + */ +public class Controller +{ + /** + * Exception thrown when an operation interruption have been requested + * (for instance by the user) + */ + public static class CanceledByUser extends Exception + { + private static final long serialVersionUID = 1L; + } + + /** + * Stack item used to recursively keep track of the task progress + */ + private static class StackItem + { + double progressAtBegin; + double atomicProgress ; + int currentPosition; + int maximumPosition; + } + + private boolean _cancelFlag; + private double _currentProgress; + private StackItem _currentOp; + private LinkedList<StackItem> _stack; + + /** + * Constructor + */ + public Controller() + { + _cancelFlag = false; + _currentProgress = 0.0; + _currentOp = new StackItem(); + _currentOp.progressAtBegin = 0.0; + _currentOp.atomicProgress = 1.0; + _currentOp.currentPosition = 0; + _currentOp.maximumPosition = 1; + _stack = new LinkedList<Controller.StackItem>(); + } + + /** + * Method to call to ask the corresponding operation to stop as soon as possible + * @warning Calling this method does not mean the computation will actually + * be interrupted. The operation is interrupted if a CanceledByUser + * exception is thrown. + */ + public void cancelComputation() + { + _cancelFlag = true; + } + + /** + * Retrieve the current progress + */ + public double getCurrentProgress() + { + return _currentProgress; + } + + /** + * Check-point function called by the thread + */ + public void checkPoint() throws CanceledByUser + { + if(_cancelFlag) { + throw new CanceledByUser(); + } + } + + /** + * Start a new check-point counter + */ + public void startCounter(int nbSteps) + { + nbSteps = Math.max(1, nbSteps); + double newAtomicProgress = _currentOp.currentPosition<_currentOp.maximumPosition ? + _currentOp.atomicProgress / nbSteps : 0.0; + StackItem newOp = new StackItem(); + newOp.progressAtBegin = _currentProgress; + newOp.atomicProgress = newAtomicProgress; + newOp.currentPosition = 0; + newOp.maximumPosition = nbSteps; + _stack.push(_currentOp); + _currentOp = newOp; + } + + /** + * Check-point function called by the thread (updating the position) + */ + public void checkPointNext() throws CanceledByUser + { + increaseCurrentStepCounter(1); + checkPoint(); + } + + /** + * Check-point function called by the thread (updating the position) + */ + public void checkPointDelta(int delta) throws CanceledByUser + { + increaseCurrentStepCounter(delta); + checkPoint(); + } + + /** + * End the currently-running check-point counter + */ + public void stopCounter() + { + _currentOp = _stack.pop(); + increaseCurrentStepCounter(1); + } + + /** + * Increase the current step counter, and refresh the overall progress value + */ + private void increaseCurrentStepCounter(int delta) + { + _currentOp.currentPosition = Math.min(_currentOp.currentPosition+delta, _currentOp.maximumPosition); + _currentProgress = _currentOp.progressAtBegin + _currentOp.atomicProgress*_currentOp.currentPosition; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLArray.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLArray.java new file mode 100644 index 0000000000000000000000000000000000000000..c68402bec2cb93a3f3768016fe4613cbabc78c85 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLArray.java @@ -0,0 +1,169 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Base class used to represent a Matlab data element, whose supports random access + */ +public abstract class MLArray extends MLObject +{ + /** + * Constructor + */ + protected MLArray(MLMeta meta) + { + super(meta); + } + + /** + * Try to return the content as a string + */ + public String getAsString() throws MLIOException + { + char[] buffer = getAsCharArray(); + String retVal = ""; + for(char c : buffer) { + retVal += c; + } + return retVal; + } + + /** + * Try to return the content as a char-valued scalar + */ + public boolean getAsLogical() throws MLIOException { ensureIsScalar(); return getAsLogicalArray()[0]; } + + /** + * Try to return the content as a char-valued scalar + */ + public char getAsChar() throws MLIOException { ensureIsScalar(); return getAsCharArray()[0]; } + + /** + * Try to return the content as a double-valued scalar + */ + public double getAsDouble() throws MLIOException { ensureIsScalar(); return getAsDoubleArray()[0]; } + + /** + * Try to return the content as a single-valued scalar + */ + public float getAsSingle() throws MLIOException { ensureIsScalar(); return getAsSingleArray()[0]; } + + /** + * Try to return the content as an int8-valued scalar + */ + public byte getAsInt8() throws MLIOException { ensureIsScalar(); return getAsInt8Array()[0]; } + + /** + * Try to return the content as an int16-valued scalar + */ + public short getAsInt16() throws MLIOException { ensureIsScalar(); return getAsInt16Array()[0]; } + + /** + * Try to return the content as an int32-valued scalar + */ + public int getAsInt32() throws MLIOException { ensureIsScalar(); return getAsInt32Array()[0]; } + + /** + * Try to return the content as an uint64-valued scalar + */ + public long getAsInt64() throws MLIOException { ensureIsScalar(); return getAsInt64Array()[0]; } + + /** + * Try to return the content as an uint8-valued scalar + */ + public byte getAsUInt8() throws MLIOException { ensureIsScalar(); return getAsUInt8Array()[0]; } + + /** + * Try to return the content as an uint16-valued scalar + */ + public short getAsUInt16() throws MLIOException { ensureIsScalar(); return getAsUInt16Array()[0]; } + + /** + * Try to return the content as an uint32-valued scalar + */ + public int getAsUInt32() throws MLIOException { ensureIsScalar(); return getAsUInt32Array()[0]; } + + /** + * Try to return the content as an uint64-valued scalar + */ + public long getAsUInt64() throws MLIOException { ensureIsScalar(); return getAsUInt64Array()[0]; } + + /** + * Try to return the content as a boolean-valued array + */ + public boolean[] getAsLogicalArray() throws MLIOException { throwBadCastException(MLType.LOGICAL); return null; } + + /** + * Try to return the content as a char-valued array + */ + public char[] getAsCharArray() throws MLIOException { throwBadCastException(MLType.CHAR); return null; } + + /** + * Try to return the content as a double-valued array + */ + public double[] getAsDoubleArray() throws MLIOException { throwBadCastException(MLType.DOUBLE); return null; } + + /** + * Try to return the content as a single-valued array + */ + public float[] getAsSingleArray() throws MLIOException { throwBadCastException(MLType.SINGLE); return null; } + + /** + * Try to return the content as an int8-valued array + */ + public byte[] getAsInt8Array() throws MLIOException { throwBadCastException(MLType.INT8); return null; } + + /** + * Try to return the content as an int16-valued array + */ + public short[] getAsInt16Array() throws MLIOException { throwBadCastException(MLType.INT16); return null; } + + /** + * Try to return the content as an int32-valued array + */ + public int[] getAsInt32Array() throws MLIOException { throwBadCastException(MLType.INT32); return null; } + + /** + * Try to return the content as an int64-valued array + */ + public long[] getAsInt64Array() throws MLIOException { throwBadCastException(MLType.INT64); return null; } + + /** + * Try to return the content as an uint8-valued array + */ + public byte[] getAsUInt8Array() throws MLIOException { throwBadCastException(MLType.UINT8); return null; } + + /** + * Try to return the content as an uint16-valued array + */ + public short[] getAsUInt16Array() throws MLIOException { throwBadCastException(MLType.UINT16); return null; } + + /** + * Try to return the content as an uint32-valued array + */ + public int[] getAsUInt32Array() throws MLIOException { throwBadCastException(MLType.UINT32); return null; } + + /** + * Try to return the content as an uint64-valued array + */ + public long[] getAsUInt64Array() throws MLIOException { throwBadCastException(MLType.UINT64); return null; } + + /** + * Exception thrown when using a non-implemented getAsTargetTypeArray() function + */ + private void throwBadCastException(MLType targetType) throws MLIOException + { + throw new MLIOException("The current MLArray is not " + targetType + "-valued."); + } + + /** + * Ensure that the current MLArray object wrap a scalar value + */ + private void ensureIsScalar() throws MLIOException + { + if(getSize()!=1) { + throw new MLIOException("The current MLArray is not a scalar."); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLArrays.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLArrays.java new file mode 100644 index 0000000000000000000000000000000000000000..5d84199dd61d94f6db757c2bf7973d87ff65d4ce --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLArrays.java @@ -0,0 +1,341 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Specialized classes implementing different types of MLArray + */ +public class MLArrays +{ + /** + * Matlab array with boolean values + */ + public static class Logical extends MLArray + { + private boolean[] _buffer; + + /** + * Return the underlying array + */ + public boolean[] get() + { + return _buffer; + } + + /** + * Constructor + */ + public Logical(MLMeta meta) + { + super(meta); + _buffer = new boolean[meta.getSize()]; + } + + @Override + public boolean[] getAsLogicalArray() throws MLIOException + { + return _buffer; + } + + /** + * Construct a boolean array from a scalar boolean value + */ + public Logical(String name, boolean value) throws MLIOException + { + this(name, new boolean[] {value}); + } + + /** + * Wrap an existing boolean array into a MLArray structure + */ + public Logical(String name, boolean[] data) throws MLIOException + { + super(new MLMeta(MLType.LOGICAL, name, new int[] {data.length}, false)); + _buffer = data; + } + } + + /** + * Matlab array with text character values + */ + public static class Char extends MLArray + { + private char[] _buffer; + + /** + * Return the underlying array + */ + public char[] get() + { + return _buffer; + } + + /** + * Constructor + */ + public Char(MLMeta meta) + { + super(meta); + _buffer = new char[meta.getSize()]; + } + + @Override + public char[] getAsCharArray() throws MLIOException + { + return _buffer; + } + + /** + * Construct a char array from a string + */ + public Char(String name, String value) throws MLIOException + { + super(new MLMeta(MLType.CHAR, name, new int[] {1, value.length()}, false)); + _buffer = new char[value.length()]; + for(int k=0; k<value.length(); ++k) { + _buffer[k] = value.charAt(k); + } + } + + /** + * Construct a char array from a scalar char value + */ + public Char(String name, char value) throws MLIOException + { + this(name, new char[] {value}); + } + + /** + * Wrap an existing char array into a MLArray structure + */ + public Char(String name, char[] data) throws MLIOException + { + super(new MLMeta(MLType.CHAR, name, new int[] {data.length}, false)); + _buffer = data; + } + } + + /** + * Matlab numeric array with double values + */ + public static class Double extends Numeric<double[]> + { + public Double(String name, double value) throws MLIOException { this(name, new double[] {value}); } + public Double(String name, double[] data) throws MLIOException { super(MLType.DOUBLE, name, data.length, data); } + public Double(MLMeta meta) { super(meta); } + + @Override + public double[] getAsDoubleArray() throws MLIOException { return getAsNativeArray(); } + + @Override + protected double[] allocate(int size) { return new double[size]; } + } + + /** + * Matlab numeric array with float values + */ + public static class Single extends Numeric<float[]> + { + public Single(String name, float value) throws MLIOException { this(name, new float[] {value}); } + public Single(String name, float[] data) throws MLIOException { super(MLType.SINGLE, name, data.length, data); } + public Single(MLMeta meta) { super(meta); } + + @Override + public float[] getAsSingleArray() throws MLIOException { return getAsNativeArray(); } + + @Override + protected float[] allocate(int size) { return new float[size]; } + } + + /** + * Matlab numeric array with signed 8 bits values + */ + public static class Int8 extends Numeric<byte[]> + { + public Int8(String name, byte value) throws MLIOException { this(name, new byte[] {value}); } + public Int8(String name, byte[] data) throws MLIOException { super(MLType.INT8, name, data.length, data); } + public Int8(MLMeta meta) { super(meta); } + + @Override + public byte[] getAsInt8Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected byte[] allocate(int size) { return new byte[size]; } + } + + /** + * Matlab numeric array with signed 16 bits values + */ + public static class Int16 extends Numeric<short[]> + { + public Int16(String name, short value) throws MLIOException { this(name, new short[] {value}); } + public Int16(String name, short[] data) throws MLIOException { super(MLType.INT16, name, data.length, data); } + public Int16(MLMeta meta) { super(meta); } + + @Override + public short[] getAsInt16Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected short[] allocate(int size) { return new short[size]; } + } + + /** + * Matlab numeric array with signed 32 bits values + */ + public static class Int32 extends Numeric<int[]> + { + public Int32(String name, int value) throws MLIOException { this(name, new int[] {value}); } + public Int32(String name, int[] data) throws MLIOException { super(MLType.INT32, name, data.length, data); } + public Int32(MLMeta meta) { super(meta); } + + @Override + public int[] getAsInt32Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected int[] allocate(int size) { return new int[size]; } + } + + /** + * Matlab numeric array with signed 64 bits values + */ + public static class Int64 extends Numeric<long[]> + { + public Int64(String name, long value) throws MLIOException { this(name, new long[] {value}); } + public Int64(String name, long[] data) throws MLIOException { super(MLType.INT64, name, data.length, data); } + public Int64(MLMeta meta) { super(meta); } + + @Override + public long[] getAsInt64Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected long[] allocate(int size) { return new long[size]; } + } + + /** + * Matlab numeric array with unsigned 8 bits values + */ + public static class UInt8 extends Numeric<byte[]> + { + public UInt8(String name, byte value) throws MLIOException { this(name, new byte[] {value}); } + public UInt8(String name, byte[] data) throws MLIOException { super(MLType.UINT8, name, data.length, data); } + public UInt8(MLMeta meta) { super(meta); } + + @Override + public byte[] getAsUInt8Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected byte[] allocate(int size) { return new byte[size]; } + } + + /** + * Matlab numeric array with unsigned 16 bits values + */ + public static class UInt16 extends Numeric<short[]> + { + public UInt16(String name, short value) throws MLIOException { this(name, new short[] {value}); } + public UInt16(String name, short[] data) throws MLIOException { super(MLType.UINT16, name, data.length, data); } + public UInt16(MLMeta meta) { super(meta); } + + @Override + public short[] getAsUInt16Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected short[] allocate(int size) { return new short[size]; } + } + + /** + * Matlab numeric array with unsigned 32 bits values + */ + public static class UInt32 extends Numeric<int[]> + { + public UInt32(String name, int value) throws MLIOException { this(name, new int[] {value}); } + public UInt32(String name, int[] data) throws MLIOException { super(MLType.UINT32, name, data.length, data); } + public UInt32(MLMeta meta) { super(meta); } + + @Override + public int[] getAsUInt32Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected int[] allocate(int size) { return new int[size]; } + } + + /** + * Matlab numeric array with unsigned 64 bits values + */ + public static class UInt64 extends Numeric<long[]> + { + public UInt64(String name, long value) throws MLIOException { this(name, new long[] {value}); } + public UInt64(String name, long[] data) throws MLIOException { super(MLType.UINT64, name, data.length, data); } + public UInt64(MLMeta meta) { super(meta); } + + @Override + public long[] getAsUInt64Array() throws MLIOException { return getAsNativeArray(); } + + @Override + protected long[] allocate(int size) { return new long[size]; } + } + + /** + * Base class for numeric arrays + */ + static abstract class Numeric<T> extends MLArray + { + private T _real ; + private T _imaginary; + + /** + * Wrap an existing array into a MLArray structure + */ + protected Numeric(MLType type, String name, int length, T data) throws MLIOException + { + super(new MLMeta(type, name, new int[] {length}, false)); + _real = data; + } + + /** + * Constructor + */ + protected Numeric(MLMeta meta) + { + super(meta); + _real = allocate(meta.getSize()); + if(getIsComplex()) { + _imaginary = allocate(meta.getSize()); + } + } + + /** + * Real components of the Matlab array + */ + public T getReal() + { + return _real; + } + + /** + * Imaginary components of the Matlab array + */ + public T getImaginary() + { + return _imaginary; + } + + /** + * Function used to allocate an array of the right size + */ + protected abstract T allocate(int size); + + /** + * Function to call to implement the getAsSomethingArray() function + * in the derived classes + */ + protected T getAsNativeArray() throws MLIOException + { + if(getIsComplex()) { + throw new MLIOException("The current MLArray is complex-valued."); + } + return _real; + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLDataInputStream.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLDataInputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..668fcaa02e135d8470fe6b8a02d69f2005f5a5f8 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLDataInputStream.java @@ -0,0 +1,1004 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.IllegalCharsetNameException; + +import plugins.ylemontag.matlabio.lib.Controller.CanceledByUser; + +/** + * + * @author Yoann Le Montagner + * + * Wrap an input stream and perform type conversion on the fly if necessary + */ +class MLDataInputStream +{ + /** + * Maximum size of the buffer + */ + private static final int CHUNK_SIZE = 0x1000; + + private InputStream _stream; + private ByteOrder _endianness; + private MLType _targetType; + private MLUtil.FieldHeader _header; + private Converter _converter; + private int _remains; + private ByteBuffer _buffer; + + /** + * Constructor + */ + MLDataInputStream(InputStream stream, ByteOrder endianness, MLType targetType) + throws IOException + { + _stream = stream; + _endianness = endianness; + _targetType = targetType; + _header = MLUtil.readFieldHeader(_stream, _endianness); + _converter = buildConverter(_targetType, _header.getType()); + _remains = _header.getDataLength(); + loadNextBuffer(); + } + + /** + * Check whether the end of the stream has been reached + */ + boolean isAtEnd() + { + return _remains==0 && _buffer.remaining()==0; + } + + /** + * Skip a given number of elements + */ + void skip(int elements) throws IOException + { + switch(_targetType) { + case LOGICAL: for(int k=0; k<elements; ++k) { consumeLogical(); } break; + case CHAR : for(int k=0; k<elements; ++k) { consumeChar (); } break; + case DOUBLE : for(int k=0; k<elements; ++k) { consumeDouble (); } break; + case SINGLE : for(int k=0; k<elements; ++k) { consumeSingle (); } break; + case INT8 : for(int k=0; k<elements; ++k) { consumeInt8 (); } break; + case INT16 : for(int k=0; k<elements; ++k) { consumeInt16 (); } break; + case INT32 : for(int k=0; k<elements; ++k) { consumeInt32 (); } break; + case INT64 : for(int k=0; k<elements; ++k) { consumeInt64 (); } break; + case UINT8 : for(int k=0; k<elements; ++k) { consumeUInt8 (); } break; + case UINT16 : for(int k=0; k<elements; ++k) { consumeUInt16 (); } break; + case UINT32 : for(int k=0; k<elements; ++k) { consumeUInt32 (); } break; + case UINT64 : for(int k=0; k<elements; ++k) { consumeUInt64 (); } break; + default: + throw new MLIOException("Cannot skip in a stream of " + _targetType + " elements"); + } + } + + /** + * Skip a given number of elements + */ + void skip(int elements, Controller controller) throws IOException, Controller.CanceledByUser + { + switch(_targetType) { + case LOGICAL: for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeLogical(); } break; + case CHAR : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeChar (); } break; + case DOUBLE : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeDouble (); } break; + case SINGLE : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeSingle (); } break; + case INT8 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeInt8 (); } break; + case INT16 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeInt16 (); } break; + case INT32 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeInt32 (); } break; + case INT64 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeInt64 (); } break; + case UINT8 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeUInt8 (); } break; + case UINT16 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeUInt16 (); } break; + case UINT32 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeUInt32 (); } break; + case UINT64 : for(int k=0; k<elements; ++k) { controller.checkPointNext(); consumeUInt64 (); } break; + default: + throw new MLIOException("Cannot skip in a stream of " + _targetType + " elements"); + } + } + + /** + * Consume a boolean-valued element + */ + boolean consumeLogical() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asLogical(_buffer); + } + + /** + * Consume a list of boolean-valued elements + */ + void consumeLogical(boolean[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeLogical(); + } + } + + /** + * Consume a list of boolean-valued elements + */ + void consumeLogical(boolean[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeLogical(); + } + } + + /** + * Consume a char-valued element + */ + char consumeChar() throws IOException + { + if(_buffer.remaining()<4 && _remains!=0) { + loadNextBuffer(); + } + return _converter.asChar(_buffer); + } + + /** + * Consume a list of char-valued elements + */ + void consumeChar(char[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeChar(); + } + } + + /** + * Consume a list of char-valued elements + */ + void consumeChar(char[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeChar(); + } + } + + /** + * Consume a double-valued element + */ + double consumeDouble() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asDouble(_buffer); + } + + /** + * Consume a list of double-valued elements + */ + void consumeDouble(double[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeDouble(); + } + } + + /** + * Consume a list of double-valued elements + */ + void consumeDouble(double[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeDouble(); + } + } + + /** + * Consume a single-valued element + */ + float consumeSingle() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asSingle(_buffer); + } + + /** + * Consume a list of single-valued elements + */ + void consumeSingle(float[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeSingle(); + } + } + + /** + * Consume a list of single-valued elements + */ + void consumeSingle(float[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeSingle(); + } + } + + /** + * Consume a int8-valued element + */ + byte consumeInt8() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asInt8(_buffer); + } + + /** + * Consume a list of int8-valued elements + */ + void consumeInt8(byte[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeInt8(); + } + } + + /** + * Consume a list of int8-valued elements + */ + void consumeInt8(byte[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeInt8(); + } + } + + /** + * Consume a int16-valued element + */ + short consumeInt16() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asInt16(_buffer); + } + + /** + * Consume a list of int16-valued elements + */ + void consumeInt16(short[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeInt16(); + } + } + + /** + * Consume a list of int16-valued elements + */ + void consumeInt16(short[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeInt16(); + } + } + + /** + * Consume a int32-valued element + */ + int consumeInt32() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asInt32(_buffer); + } + + /** + * Consume a list of int32-valued elements + */ + void consumeInt32(int[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeInt32(); + } + } + + /** + * Consume a list of int32-valued elements + */ + void consumeInt32(int[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeInt32(); + } + } + + /** + * Consume a int64-valued element + */ + long consumeInt64() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asInt64(_buffer); + } + + /** + * Consume a list of int64-valued elements + */ + void consumeInt64(long[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeInt64(); + } + } + + /** + * Consume a list of int64-valued elements + */ + void consumeInt64(long[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeInt64(); + } + } + + /** + * Consume a uint8-valued element + */ + byte consumeUInt8() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asUInt8(_buffer); + } + + /** + * Consume a list of uint8-valued elements + */ + void consumeUInt8(byte[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeUInt8(); + } + } + + /** + * Consume a list of uint8-valued elements + */ + void consumeUInt8(byte[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeUInt8(); + } + } + + /** + * Consume a uint16-valued element + */ + short consumeUInt16() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asUInt16(_buffer); + } + + /** + * Consume a list of uint16-valued elements + */ + void consumeUInt16(short[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeUInt16(); + } + } + + /** + * Consume a list of uint16-valued elements + */ + void consumeUInt16(short[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeUInt16(); + } + } + + /** + * Consume a uint32-valued element + */ + int consumeUInt32() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asUInt32(_buffer); + } + + /** + * Consume a list of uint32-valued elements + */ + void consumeUInt32(int[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeUInt32(); + } + } + + /** + * Consume a list of uint32-valued elements + */ + void consumeUInt32(int[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeUInt32(); + } + } + + /** + * Consume a uint64-valued element + */ + long consumeUInt64() throws IOException + { + if(_buffer.remaining()==0) { + loadNextBuffer(); + } + return _converter.asUInt64(_buffer); + } + + /** + * Consume a list of uint64-valued elements + */ + void consumeUInt64(long[] out) throws IOException + { + for(int k=0; k<out.length; ++k) { + out[k] = consumeUInt64(); + } + } + + /** + * Consume a list of uint64-valued elements + */ + void consumeUInt64(long[] out, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<out.length; ++k) { + controller.checkPointNext(); + out[k] = consumeUInt64(); + } + } + + /** + * Re-fill the buffer + */ + private void loadNextBuffer() throws IOException + { + int alreadyInBuffer = _buffer==null ? 0 : _buffer.remaining(); + int toBeRead = Math.min(CHUNK_SIZE - alreadyInBuffer, _remains); + int newBufferLength = toBeRead + alreadyInBuffer; + if(_buffer!=null && toBeRead<=0) { + throw new MLUtil.EndOfStream(); + } + ByteBuffer newBuffer = MLUtil.allocate(_endianness, newBufferLength); + if(_buffer!=null && alreadyInBuffer>0) { + MLUtil.copy(newBuffer, 0, _buffer, _buffer.position(), alreadyInBuffer); + newBuffer.position(alreadyInBuffer); + } + _buffer = newBuffer; + MLUtil.consume(_stream, _buffer, _buffer.position(), toBeRead); + _remains -= toBeRead; + if(_remains==0) { + MLUtil.consume(_stream, _endianness, _header.getPaddingLength()); + } + } + + /** + * Interface for type conversion on the fly + */ + private static abstract class Converter + { + /** + * Conversion to logical + */ + public boolean asLogical(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to logical forbidden"); + } + + /** + * Conversion to char + */ + public char asChar(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to char forbidden"); + } + + /** + * Conversion to double + */ + public double asDouble(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to double forbidden"); + } + + /** + * Conversion to single + */ + public float asSingle(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to single forbidden"); + } + + /** + * Conversion to int8 + */ + public byte asInt8(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to int8 forbidden"); + } + + /** + * Conversion to int16 + */ + public short asInt16(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to int16 forbidden"); + } + + /** + * Conversion to int32 + */ + public int asInt32(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to int32 forbidden"); + } + + /** + * Conversion to int64 + */ + public long asInt64(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to int64 forbidden"); + } + + /** + * Conversion to uint8 + */ + public byte asUInt8(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to uint8 forbidden"); + } + + /** + * Conversion to uint16 + */ + public short asUInt16(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to uint16 forbidden"); + } + + /** + * Conversion to uint32 + */ + public int asUInt32(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to uint32 forbidden"); + } + + /** + * Conversion to uint64 + */ + public long asUInt64(ByteBuffer input) throws MLIOException + { + throw new MLIOException("Conversion to uint64 forbidden"); + } + } + + /** + * Default converter, performing no conversion (just a bulk copy) + */ + private static class CopyConverter extends Converter + { + @Override + public char asChar(ByteBuffer input) throws MLIOException + { + return input.getChar(); + } + + @Override + public double asDouble(ByteBuffer input) throws MLIOException + { + return input.getDouble(); + } + + @Override + public float asSingle(ByteBuffer input) throws MLIOException + { + return input.getFloat(); + } + + @Override + public byte asInt8(ByteBuffer input) throws MLIOException + { + return input.get(); + } + + @Override + public short asInt16(ByteBuffer input) throws MLIOException + { + return input.getShort(); + } + + @Override + public int asInt32(ByteBuffer input) throws MLIOException + { + return input.getInt(); + } + + @Override + public long asInt64(ByteBuffer input) throws MLIOException + { + return input.getLong(); + } + + @Override + public byte asUInt8(ByteBuffer input) throws MLIOException + { + return input.get(); + } + + @Override + public short asUInt16(ByteBuffer input) throws MLIOException + { + return input.getShort(); + } + + @Override + public int asUInt32(ByteBuffer input) throws MLIOException + { + return input.getInt(); + } + + @Override + public long asUInt64(ByteBuffer input) throws MLIOException + { + return input.getLong(); + } + } + + /** + * Boolean extraction + */ + private static class LogicalConverter extends Converter + { + @Override + public boolean asLogical(ByteBuffer input) throws MLIOException + { + return input.get()!=0; + } + } + + /** + * Charset conversions + */ + private static class CharConverter extends Converter + { + private CharsetDecoder _decoder; + + public CharConverter(String charsetName) throws MLIOException + { + try { + _decoder = Charset.forName(charsetName).newDecoder(); + } + catch(IllegalCharsetNameException e) { + throw new MLIOException("Unknown charset: " + charsetName); + } + } + + @Override + public char asChar(ByteBuffer input) throws MLIOException + { + ByteBuffer output = ByteBuffer.wrap(new byte[2]); + if(convertChars(output, input)!=1) { + throw new MLIOException("Unable to extract a char from the current stream"); + } + return output.getChar(); + } + + /** + * Core conversion function + */ + private int convertChars(ByteBuffer output, ByteBuffer input) + { + CharBuffer castedOutput = output.asCharBuffer(); + _decoder.decode(input, castedOutput, true); + return castedOutput.position(); + } + } + + /** + * Single conversions + */ + private static abstract class SingleConverter extends Converter + { + @Override + public float asSingle(ByteBuffer input) throws MLIOException + { + return convertNextSingle(input); + } + + /** + * Core conversion function + */ + protected abstract float convertNextSingle(ByteBuffer input); + } + + /** + * Double conversions + */ + private static abstract class DoubleConverter extends Converter + { + @Override + public double asDouble(ByteBuffer input) throws MLIOException + { + return convertNextDouble(input); + } + + /** + * Core conversion function + */ + protected abstract double convertNextDouble(ByteBuffer input); + } + + /** + * Return a type converter object, or throw an exception if the type conversion is not possible + */ + private static Converter buildConverter(MLType targetType, MLRawType rawType) + throws MLIOException + { + switch(targetType) + { + // Boolean case + case LOGICAL: + switch(rawType) + { + case UINT8: return new LogicalConverter(); + default: break; + } + break; + + // Char cases + case CHAR: + switch(rawType) + { + case UTF16: return new CopyConverter(); + case UTF8 : return new CharConverter("UTF-8"); + default: break; + } + break; + + // Integer cases + case INT8 : if(rawType==MLRawType.INT8 ) return new CopyConverter(); break; + case UINT8 : if(rawType==MLRawType.UINT8 ) return new CopyConverter(); break; + case INT16 : if(rawType==MLRawType.INT16 ) return new CopyConverter(); break; + case UINT16: if(rawType==MLRawType.UINT16) return new CopyConverter(); break; + case INT32 : if(rawType==MLRawType.INT32 ) return new CopyConverter(); break; + case UINT32: if(rawType==MLRawType.UINT32) return new CopyConverter(); break; + case INT64 : if(rawType==MLRawType.INT64 ) return new CopyConverter(); break; + case UINT64: if(rawType==MLRawType.UINT64) return new CopyConverter(); break; + + // Float case + case SINGLE: + switch(rawType) + { + // Native case + case SINGLE: return new CopyConverter(); + + // Int8 to single + case INT8: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + return input.get(); + } + }; + + // UInt8 to single + case UINT8: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + float v = input.get(); + if(v<0) { + v += 256; + } + return v; + } + }; + + // Int16 to single + case INT16: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + return input.getShort(); + } + }; + + // UInt16 to single + case UINT16: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + float v = input.getShort(); + if(v<0) { + v += 65536; + } + return v; + } + }; + + // Int32 to single + case INT32: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + return input.getInt(); + } + }; + + // UInt32 to single + case UINT32: return new SingleConverter() + { + @Override + protected float convertNextSingle(ByteBuffer input) { + float v = input.getInt(); + if(v<0) { + v += (65536L * 65536L); + } + return v; + } + }; + + // Other cases + default: + break; + } + break; + + // Double case + case DOUBLE: + switch(rawType) + { + // Native case + case DOUBLE: return new CopyConverter(); + + // Single to double + case SINGLE: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + return input.getFloat(); + } + }; + + // Int8 to double + case INT8: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + return input.get(); + } + }; + + // UInt8 to double + case UINT8: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + double v = input.get(); + if(v<0) { + v += 256; + } + return v; + } + }; + + // Int16 to double + case INT16: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + return input.getShort(); + } + }; + + // UInt16 to double + case UINT16: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + double v = input.getShort(); + if(v<0) { + v += 65536; + } + return v; + } + }; + + // Int32 to double + case INT32: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + return input.getInt(); + } + }; + + // UInt32 to double + case UINT32: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + double v = input.getInt(); + if(v<0) { + v += (65536L * 65536L); + } + return v; + } + }; + + // Int64 to double + case INT64: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + return input.getLong(); + } + }; + + // UInt64 to double + case UINT64: return new DoubleConverter() + { + @Override + protected double convertNextDouble(ByteBuffer input) { + double v = input.getLong(); + if(v<0) { + v += Long.MAX_VALUE; + v += Long.MAX_VALUE; + v += 2; + } + return v; + } + }; + + // Other cases + default: + break; + } + break; + + // Other cases + default: + break; + } + throw new MLIOException("Cannot interpret " + rawType + " as " + targetType); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLDataOutputStream.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLDataOutputStream.java new file mode 100644 index 0000000000000000000000000000000000000000..d6cc7f099ae83ebb5c59a1df9a891d3c0f763bbf --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLDataOutputStream.java @@ -0,0 +1,531 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import plugins.ylemontag.matlabio.lib.Controller.CanceledByUser; + +/** + * + * @author Yoann Le Montagner + * + * Wrap an output stream to write a (potentially) long Matlab field + */ +public class MLDataOutputStream +{ + /** + * Maximum size of the buffer + */ + private static final int CHUNK_SIZE = 0x1000; + + private OutputStream _stream; + private ByteOrder _endianness; + private MLType _sourceType; + private int _encodingTypeSize; + private MLUtil.FieldHeader _header; + private int _remains; + private ByteBuffer _buffer; + + /** + * Constructor + */ + MLDataOutputStream(OutputStream stream, ByteOrder endianness, MLType sourceType, int elements) + throws IOException + { + _stream = stream; + _endianness = endianness; + _sourceType = sourceType; + _encodingTypeSize = sourceType.getRawType().getSize(); + _remains = elements * _encodingTypeSize; + _header = new MLUtil.FieldHeader(sourceType.getRawType(), _remains); + MLUtil.writeFieldHeader(_stream, _endianness, _header); + writeCurrentAndPrepareNextBuffer(); + } + + /** + * Check whether the end of the stream has been reached + */ + boolean isAtEnd() + { + return _buffer==null; + } + + /** + * Skip a given number of elements + */ + void skip(int elements) throws IOException + { + int dataLength = _encodingTypeSize; + switch(_sourceType) { + case LOGICAL: + case CHAR : + case DOUBLE : + case SINGLE : + case INT8 : + case INT16 : + case INT32 : + case INT64 : + case UINT8 : + case UINT16 : + case UINT32 : + case UINT64 : + for(int k=0; k<elements; ++k) { + rawSkip(dataLength); + } + break; + + default: + throw new MLIOException("Cannot skip in a stream of " + _sourceType + " elements"); + } + } + + /** + * Skip a given number of elements + */ + void skip(int elements, Controller controller) throws IOException, Controller.CanceledByUser + { + int dataLength = _encodingTypeSize; + switch(_sourceType) { + case LOGICAL: + case CHAR : + case DOUBLE : + case SINGLE : + case INT8 : + case INT16 : + case INT32 : + case INT64 : + case UINT8 : + case UINT16 : + case UINT32 : + case UINT64 : + for(int k=0; k<elements; ++k) { + controller.checkPointNext(); + rawSkip(dataLength); + } + break; + + default: + throw new MLIOException("Cannot skip in a stream of " + _sourceType + " elements"); + } + } + + /** + * Skip one element + */ + private void rawSkip(int dataLength) throws IOException + { + _buffer.position(_buffer.position() + dataLength); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new boolean-valued element + */ + void pushLogical(boolean in) throws IOException + { + _buffer.put(in ? (byte)1 : (byte)0); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of boolean-valued elements + */ + void pushLogical(boolean[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushLogical(in[k]); + } + } + + /** + * Push a new list of boolean-valued elements + */ + void pushLogical(boolean[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushLogical(in[k]); + } + } + + /** + * Push a new char-valued element + */ + void pushChar(char in) throws IOException + { + _buffer.putChar(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of char-valued elements + */ + void pushChar(char[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushChar(in[k]); + } + } + + /** + * Push a new list of char-valued elements + */ + void pushChar(char[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushChar(in[k]); + } + } + + /** + * Push a new double-valued element + */ + void pushDouble(double in) throws IOException + { + _buffer.putDouble(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of double-valued elements + */ + void pushDouble(double[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushDouble(in[k]); + } + } + + /** + * Push a new list of double-valued elements + */ + void pushDouble(double[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushDouble(in[k]); + } + } + + /** + * Push a new single-valued element + */ + void pushSingle(float in) throws IOException + { + _buffer.putFloat(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of single-valued elements + */ + void pushSingle(float[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushSingle(in[k]); + } + } + + /** + * Push a new list of single-valued elements + */ + void pushSingle(float[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushSingle(in[k]); + } + } + + /** + * Push a new int8-valued element + */ + void pushInt8(byte in) throws IOException + { + _buffer.put(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of int8-valued elements + */ + void pushInt8(byte[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushInt8(in[k]); + } + } + + /** + * Push a new list of int8-valued elements + */ + void pushInt8(byte[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushInt8(in[k]); + } + } + + /** + * Push a new int16-valued element + */ + void pushInt16(short in) throws IOException + { + _buffer.putShort(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of int16-valued elements + */ + void pushInt16(short[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushInt16(in[k]); + } + } + + /** + * Push a new list of int16-valued elements + */ + void pushInt16(short[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushInt16(in[k]); + } + } + + /** + * Push a new int32-valued element + */ + void pushInt32(int in) throws IOException + { + _buffer.putInt(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of int32-valued elements + */ + void pushInt32(int[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushInt32(in[k]); + } + } + + /** + * Push a new list of int32-valued elements + */ + void pushInt32(int[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushInt32(in[k]); + } + } + + /** + * Push a new int64-valued element + */ + void pushInt64(long in) throws IOException + { + _buffer.putLong(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of int64-valued elements + */ + void pushInt64(long[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushInt64(in[k]); + } + } + + /** + * Push a new list of int64-valued elements + */ + void pushInt64(long[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushInt64(in[k]); + } + } + + /** + * Push a new uint8-valued element + */ + void pushUInt8(byte in) throws IOException + { + _buffer.put(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of uint8-valued elements + */ + void pushUInt8(byte[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushUInt8(in[k]); + } + } + + /** + * Push a new list of uint8-valued elements + */ + void pushUInt8(byte[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushUInt8(in[k]); + } + } + + /** + * Push a new uint16-valued element + */ + void pushUInt16(short in) throws IOException + { + _buffer.putShort(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of uint16-valued elements + */ + void pushUInt16(short[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushUInt16(in[k]); + } + } + + /** + * Push a new list of uint16-valued elements + */ + void pushUInt16(short[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushUInt16(in[k]); + } + } + + /** + * Push a new uint32-valued element + */ + void pushUInt32(int in) throws IOException + { + _buffer.putInt(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of uint32-valued elements + */ + void pushUInt32(int[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushUInt32(in[k]); + } + } + + /** + * Push a new list of uint32-valued elements + */ + void pushUInt32(int[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushUInt32(in[k]); + } + } + + /** + * Push a new uint64-valued element + */ + void pushUInt64(long in) throws IOException + { + _buffer.putLong(in); + if(_buffer.remaining()==0) { + writeCurrentAndPrepareNextBuffer(); + } + } + + /** + * Push a new list of uint64-valued elements + */ + void pushUInt64(long[] in) throws IOException + { + for(int k=0; k<in.length; ++k) { + pushUInt64(in[k]); + } + } + + /** + * Push a new list of uint64-valued elements + */ + void pushUInt64(long[] in, Controller controller) throws IOException, CanceledByUser + { + for(int k=0; k<in.length; ++k) { + controller.checkPointNext(); + pushUInt64(in[k]); + } + } + + /** + * Write the current buffer and prepare the next one + */ + private void writeCurrentAndPrepareNextBuffer() throws IOException + { + // Write the current buffer + if(_buffer!=null) { + MLUtil.push(_stream, _buffer); + _remains -= _buffer.limit(); + } + + // Prepare the next buffer, or write the padding bytes if the end of the + // stream have been reached + if(_remains==0) { + _buffer = null; + MLUtil.skip(_stream, _header.getPaddingLength()); + } + else { + int newBufferLength = Math.min(CHUNK_SIZE, _remains); + _buffer = MLUtil.allocate(_endianness, newBufferLength); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLIOException.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLIOException.java new file mode 100644 index 0000000000000000000000000000000000000000..130375c19bb6c4944998a673ca4a720e82f984e5 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLIOException.java @@ -0,0 +1,30 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; + +/** + * + * @author Yoann Le Montagner + * + * Exception thrown by the Matlab IO library + */ +public class MLIOException extends IOException +{ + private static final long serialVersionUID = 1L; + + /** + * Default constructor + */ + public MLIOException() + { + super(); + } + + /** + * Constructor + */ + public MLIOException(String message) + { + super(message); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLIStream.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLIStream.java new file mode 100644 index 0000000000000000000000000000000000000000..1de0d50591c68808cd2c125b08e82ae85363be6e --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLIStream.java @@ -0,0 +1,35 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; + +import plugins.ylemontag.matlabio.lib.MLMeta; +import plugins.ylemontag.matlabio.lib.MLObject; +import plugins.ylemontag.matlabio.lib.Controller; + +/** + * + * @author Yoann Le Montagner + * + * Base class used to represent a Matlab data element, whose content is read + * dynamically from the underlying Matlab file + */ +public abstract class MLIStream extends MLObject +{ + /** + * Constructor + */ + protected MLIStream(MLMeta meta) + { + super(meta); + } + + /** + * Skip a given number of elements + */ + public abstract void skip(int num) throws IOException; + + /** + * Skip a given number of elements (with thread-control) + */ + public abstract void skip(int num, Controller controller) throws IOException, Controller.CanceledByUser; +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLIStreams.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLIStreams.java new file mode 100644 index 0000000000000000000000000000000000000000..1b8e6df5bb8382939450aca3daa3903407efae6d --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLIStreams.java @@ -0,0 +1,488 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteOrder; + +/** + * + * @author Yoann Le Montagner + * + * Specialized classes implementing different types of MLIStream + */ +public class MLIStreams +{ + /** + * Matlab input stream with boolean values + */ + public static class Logical extends Numeric ///TODO: should not inherit from Numeric + { + /** + * Read the next value + */ + public boolean get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private boolean _currentValue; + + Logical(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = false; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeLogical(); + } + } + + /** + * Matlab input stream with double values + */ + public static class Double extends Numeric + { + /** + * Read the next value + */ + public double get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private double _currentValue; + + Double(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0.0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeDouble(); + } + } + + /** + * Matlab input stream with float values + */ + public static class Single extends Numeric + { + /** + * Read the next value + */ + public float get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private float _currentValue; + + Single(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0.0f; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeSingle(); + } + } + + /** + * Matlab input stream with signed 8 bits values + */ + public static class Int8 extends Numeric + { + /** + * Read the next value + */ + public byte get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private byte _currentValue; + + Int8(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeInt8(); + } + } + + /** + * Matlab input stream with signed 16 bits values + */ + public static class Int16 extends Numeric + { + /** + * Read the next value + */ + public short get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private short _currentValue; + + Int16(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeInt16(); + } + } + + /** + * Matlab input stream with signed 32 bits values + */ + public static class Int32 extends Numeric + { + /** + * Read the next value + */ + public int get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private int _currentValue; + + Int32(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeInt32(); + } + } + + /** + * Matlab input stream with signed 64 bits values + */ + public static class Int64 extends Numeric + { + /** + * Read the next value + */ + public long get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private long _currentValue; + + Int64(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeInt64(); + } + } + + /** + * Matlab input stream with unsigned 8 bits values + */ + public static class UInt8 extends Numeric + { + /** + * Read the next value + */ + public byte get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private byte _currentValue; + + UInt8(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeUInt8(); + } + } + + /** + * Matlab input stream with unsigned 16 bits values + */ + public static class UInt16 extends Numeric + { + /** + * Read the next value + */ + public short get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private short _currentValue; + + UInt16(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeUInt16(); + } + } + + /** + * Matlab input stream with unsigned 32 bits values + */ + public static class UInt32 extends Numeric + { + /** + * Read the next value + */ + public int get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private int _currentValue; + + UInt32(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeUInt32(); + } + } + + /** + * Matlab input stream with unsigned 64 bits values + */ + public static class UInt64 extends Numeric + { + /** + * Read the next value + */ + public long get() throws IOException + { + readNextValue(); + return _currentValue; + } + + private long _currentValue; + + UInt64(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta, source, endianness); + _currentValue = 0; + } + + @Override + protected void loadNextValue(MLDataInputStream stream) throws IOException + { + _currentValue = stream.consumeUInt64(); + } + } + + /** + * Matlab numeric input stream + */ + static abstract class Numeric extends MLIStream + { + /** + * Current section in the stream + */ + private static enum Section + { + REAL , + IMAGINARY, + EOS ; + } + + private InputStream _source ; + private ByteOrder _endianness ; + private Section _currentSection; + private MLDataInputStream _dataStream ; + private int _nextIndex ; + + /** + * Constructor + */ + protected Numeric(MLMeta meta, InputStream source, ByteOrder endianness) throws IOException + { + super(meta); + _source = source; + _endianness = endianness; + loadNextSection(); + } + + /** + * Check whether the end of both the real and the imaginary streams has been reached + */ + public boolean isAtEnd() + { + return _currentSection==Section.EOS; + } + + /** + * Return true if the the cursor is not at the end of the stream and if the next + * value to be read belongs to the real data stream + */ + public boolean isRealPart() + { + return _currentSection==Section.REAL; + } + + /** + * Return true if the the cursor is not at the end of the stream and if the next + * value to be read belongs to the real data stream + */ + public boolean isImaginaryPart() + { + return _currentSection==Section.IMAGINARY; + } + + /** + * Return the index of the next element + */ + public int getNextIndex() + { + return _nextIndex; + } + + /** + * The derived class should save the data element encoded in buffer at buffer.position() + * when this function is called + */ + protected abstract void loadNextValue(MLDataInputStream stream) throws IOException; + + /** + * The derived class should call this method at each read request. This method + * will call 'loadNextValue' with the correct parameter values + */ + protected void readNextValue() throws IOException + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + loadNextValue(_dataStream); + ++_nextIndex; + if(_dataStream.isAtEnd()) { + loadNextSection(); + } + } + + /** + * Create the data stream corresponding to the next data section (either the + * real part or the imaginary part) + */ + private void loadNextSection() throws IOException + { + // Update the section flag + if(_currentSection==null) { + _currentSection = Section.REAL; + } + else if(_currentSection==Section.REAL && getIsComplex()) { + _currentSection = Section.IMAGINARY; + } + else { + _currentSection = Section.EOS; + _dataStream = null; + return; + } + + // Load the next section + _dataStream = new MLDataInputStream(_source, _endianness, getType()); + _nextIndex = 0; + if(_dataStream.isAtEnd()) { + _currentSection = Section.EOS; + _dataStream = null; + } + } + + @Override + public void skip(int num) throws IOException + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + int remainingElementsInCurrentSection = getSize()-_nextIndex; + if(num < remainingElementsInCurrentSection) { + _nextIndex += num; + _dataStream.skip(num); + } + else { + _dataStream.skip(remainingElementsInCurrentSection); + loadNextSection(); + _nextIndex = num - remainingElementsInCurrentSection; + _dataStream.skip(_nextIndex); + } + } + + @Override + public void skip(int num, Controller controller) throws IOException, Controller.CanceledByUser + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + int remainingElementsInCurrentSection = getSize()-_nextIndex; + if(num < remainingElementsInCurrentSection) { + _nextIndex += num; + _dataStream.skip(num, controller); + } + else { + _dataStream.skip(remainingElementsInCurrentSection, controller); + loadNextSection(); + _nextIndex = num - remainingElementsInCurrentSection; + _dataStream.skip(_nextIndex, controller); + } + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLMeta.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLMeta.java new file mode 100644 index 0000000000000000000000000000000000000000..d30703afcfeb54c3f1c1a09a5c24cb3ccb3d97ef --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLMeta.java @@ -0,0 +1,117 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Object containing all meta-informations relative to a Matlab data element + */ +public class MLMeta +{ + private MLType _type; + private String _name; + private int[] _dimensions; + private int _size; + private boolean _isComplex; + + /** + * Constructor + */ + public MLMeta(MLType type, String name, int[] dimensions, boolean isComplex) throws MLIOException + { + _type = type; + _name = name; + _isComplex = isComplex; + if(!isValidName(_name)) { + throw new MLIOException("Invalid name for a Matlab object: " + _name); + } + if(_isComplex && !(_type.getIsNumeric() || _type==MLType.SPARSE)) { + throw new MLIOException("Non-numeric objects must have a complex flag set to false"); + } + _dimensions = dimensions; + _size = 1; + for(int d : _dimensions) { + _size *= d; + } + } + + /** + * Data element type + */ + public MLType getType() + { + return _type; + } + + /** + * Name of the data element type + */ + public String getName() + { + return _name; + } + + /** + * Dimensions of the data element + */ + public int[] getDimensions() + { + return _dimensions; + } + + /** + * Dimensions of the data element as a human readable string + */ + public String getDimensionsAsString() + { + int nDims = _dimensions.length; + String retVal = ""; + for(int k=0; k<nDims; ++k) { + if(k!=0) { + retVal +="\u00d7"; + } + retVal += _dimensions[k]; + } + return retVal; + } + + /** + * Number of components, i.e. product of all dimensions + */ + public int getSize() + { + return _size; + } + + /** + * Whether the underlying object contains complex data or not + * @warning This make sense only in the case of numeric or sparse class. + * Otherwise, the function returns false. + */ + public boolean getIsComplex() + { + return _isComplex; + } + + /** + * Check whether a string is a valid Matlab variable name + */ + public static boolean isValidName(String name) + { + int length = name.length(); + if(length==0) { + return false; + } + for(int k=0; k<length; ++k) { + char currentChar = name.charAt(k); + if(!( + (currentChar>='A' && currentChar<='Z') || + (currentChar>='a' && currentChar<='z') || + (currentChar>='0' && currentChar<='9' && k!=0) || currentChar=='_' + )) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLOStream.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLOStream.java new file mode 100644 index 0000000000000000000000000000000000000000..72c05e04f5f3e78539210ec75ad8c3e1423c05ad --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLOStream.java @@ -0,0 +1,34 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; + +import plugins.ylemontag.matlabio.lib.MLMeta; +import plugins.ylemontag.matlabio.lib.MLObject; + +/** + * + * @author Yoann Le Montagner + * + * Base class used to represent a Matlab data element, whose content is written + * dynamically to an underlying Matlab file + */ +public abstract class MLOStream extends MLObject +{ + /** + * Constructor + */ + protected MLOStream(MLMeta meta) + { + super(meta); + } + + /** + * Skip a given number of elements + */ + public abstract void skip(int num) throws IOException; + + /** + * Skip a given number of elements (with thread-control) + */ + public abstract void skip(int num, Controller controller) throws IOException, Controller.CanceledByUser; +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLOStreams.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLOStreams.java new file mode 100644 index 0000000000000000000000000000000000000000..6f0300805b5a5a18cd29b572dc1e6fdbc7878c0e --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLOStreams.java @@ -0,0 +1,453 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteOrder; + +/** + * + * @author Yoann Le Montagner + * + * Specialized classes implementing different types of MLOStream + */ +public class MLOStreams +{ + /** + * Matlab numeric output stream with double values + */ + public static class Double extends Numeric + { + /** + * Write the next value + */ + public void put(double value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private double _currentValue; + + Double(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0.0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushDouble(_currentValue); + } + } + + /** + * Matlab numeric output stream with float values + */ + public static class Single extends Numeric + { + /** + * Write the next value + */ + public void put(float value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private float _currentValue; + + Single(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0.0f; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushSingle(_currentValue); + } + } + + /** + * Matlab numeric output stream with signed 8 bits values + */ + public static class Int8 extends Numeric + { + /** + * Write the next value + */ + public void put(byte value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private byte _currentValue; + + Int8(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushInt8(_currentValue); + } + } + + /** + * Matlab numeric output stream with signed 16 bits values + */ + public static class Int16 extends Numeric + { + /** + * Write the next value + */ + public void put(short value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private short _currentValue; + + Int16(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushInt16(_currentValue); + } + } + + /** + * Matlab numeric output stream with signed 32 bits values + */ + public static class Int32 extends Numeric + { + /** + * Write the next value + */ + public void put(int value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private int _currentValue; + + Int32(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushInt32(_currentValue); + } + } + + /** + * Matlab numeric output stream with signed 64 bits values + */ + public static class Int64 extends Numeric + { + /** + * Write the next value + */ + public void put(long value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private long _currentValue; + + Int64(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushInt64(_currentValue); + } + } + + /** + * Matlab numeric output stream with unsigned 8 bits values + */ + public static class UInt8 extends Numeric + { + /** + * Write the next value + */ + public void put(byte value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private byte _currentValue; + + UInt8(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushUInt8(_currentValue); + } + } + + /** + * Matlab numeric output stream with unsigned 16 bits values + */ + public static class UInt16 extends Numeric + { + /** + * Write the next value + */ + public void put(short value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private short _currentValue; + + UInt16(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushUInt16(_currentValue); + } + } + + /** + * Matlab numeric output stream with unsigned 32 bits values + */ + public static class UInt32 extends Numeric + { + /** + * Write the next value + */ + public void put(int value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private int _currentValue; + + UInt32(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushUInt32(_currentValue); + } + } + + /** + * Matlab numeric output stream with unsigned 64 bits values + */ + public static class UInt64 extends Numeric + { + /** + * Write the next value + */ + public void put(long value) throws IOException + { + _currentValue = value; + writeNextValue(); + } + + private long _currentValue; + + UInt64(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta, target, endianness); + _currentValue = 0; + } + + @Override + protected void saveNextValue(MLDataOutputStream stream) throws IOException + { + stream.pushUInt64(_currentValue); + } + } + + /** + * Matlab numeric output stream + */ + static abstract class Numeric extends MLOStream + { + /** + * Current section in the stream + */ + private static enum Section + { + REAL , + IMAGINARY, + EOS ; + } + + private OutputStream _target ; + private ByteOrder _endianness ; + private Section _currentSection; + private MLDataOutputStream _dataStream ; + private int _nextIndex ; + + /** + * Constructor + */ + protected Numeric(MLMeta meta, OutputStream target, ByteOrder endianness) throws IOException + { + super(meta); + _target = target; + _endianness = endianness; + prepareNextSection(); + } + + /** + * Check whether the end of both the real and the imaginary streams has been reached + */ + public boolean isAtEnd() + { + return _currentSection==Section.EOS; + } + + /** + * Return true if the the cursor is not at the end of the stream and if the next + * value to be read belongs to the real data stream + */ + public boolean isRealPart() + { + return _currentSection==Section.REAL; + } + + /** + * Return true if the the cursor is not at the end of the stream and if the next + * value to be read belongs to the real data stream + */ + public boolean isImaginaryPart() + { + return _currentSection==Section.IMAGINARY; + } + + /** + * The derived class should push the previously saved data element in the stream + * when this function is called + */ + protected abstract void saveNextValue(MLDataOutputStream stream) throws IOException; + + /** + * The derived class should call this method at each write request. This method + * will call 'saveNextValue' with the correct parameter values + */ + protected void writeNextValue() throws IOException + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + saveNextValue(_dataStream); + ++_nextIndex; + if(_dataStream.isAtEnd()) { + prepareNextSection(); + } + } + + /** + * Create the data stream corresponding to the next data section (either the + * real part or the imaginary part) + */ + private void prepareNextSection() throws IOException + { + // Update the section flag + if(_currentSection==null) { + _currentSection = Section.REAL; + } + else if(_currentSection==Section.REAL && getIsComplex()) { + _currentSection = Section.IMAGINARY; + } + else { + _currentSection = Section.EOS; + _dataStream = null; + _target.close(); + return; + } + + // Prepare the next section + _dataStream = new MLDataOutputStream(_target, _endianness, getType(), getSize()); + _nextIndex = 0; + if(_dataStream.isAtEnd()) { + _currentSection = Section.EOS; + _dataStream = null; + _target.close(); + } + } + + @Override + public void skip(int num) throws IOException + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + int remainingElementsInCurrentSection = getSize()-_nextIndex; + if(num < remainingElementsInCurrentSection) { + _nextIndex += num; + _dataStream.skip(num); + } + else { + _dataStream.skip(remainingElementsInCurrentSection); + prepareNextSection(); + _nextIndex = num - remainingElementsInCurrentSection; + _dataStream.skip(_nextIndex); + } + } + + @Override + public void skip(int num, Controller controller) throws IOException, Controller.CanceledByUser + { + if(_dataStream==null) { + throw new MLUtil.EndOfStream(); + } + int remainingElementsInCurrentSection = getSize()-_nextIndex; + if(num < remainingElementsInCurrentSection) { + _nextIndex += num; + _dataStream.skip(num, controller); + } + else { + _dataStream.skip(remainingElementsInCurrentSection, controller); + prepareNextSection(); + _nextIndex = num - remainingElementsInCurrentSection; + _dataStream.skip(_nextIndex, controller); + } + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLObject.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLObject.java new file mode 100644 index 0000000000000000000000000000000000000000..11d7e8230619ca4737731eb37fc95bc14b353b83 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLObject.java @@ -0,0 +1,70 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Base class used to represent a Matlab data element + */ +public class MLObject +{ + private MLMeta _meta; + + /** + * Constructor + */ + protected MLObject(MLMeta meta) + { + _meta = meta; + } + + /** + * All the meta-data associated to the object + */ + public MLMeta getMeta() + { + return _meta; + } + + /** + * Data element type + */ + public MLType getType() + { + return _meta.getType(); + } + + /** + * Name of the data element type + */ + public String getName() + { + return _meta.getName(); + } + + /** + * Dimensions of the data element + */ + public int[] getDimensions() + { + return _meta.getDimensions(); + } + + /** + * Number of components, i.e. product of all dimensions + */ + public int getSize() + { + return _meta.getSize(); + } + + /** + * Whether the underlying object contains complex data or not + * @warning This make sense only in the case of numeric or sparse class. + * Otherwise, the function returns false. + */ + public boolean getIsComplex() + { + return _meta.getIsComplex(); + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLRawType.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLRawType.java new file mode 100644 index 0000000000000000000000000000000000000000..6f711526ce4d022abda85d695c95cfbeb25c5cf8 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLRawType.java @@ -0,0 +1,92 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Type of a field in a .mat file (correspond to constants miFOO in the Matlab .mat file reference) + */ +public enum MLRawType +{ + INT8 ((byte) 1, 1, "int8" ), + UINT8 ((byte) 2, 1, "uint8" ), + INT16 ((byte) 3, 2, "int16" ), + UINT16 ((byte) 4, 2, "uint16" ), + INT32 ((byte) 5, 4, "int32" ), + UINT32 ((byte) 6, 4, "uint32" ), + INT64 ((byte)12, 8, "int64" ), + UINT64 ((byte)13, 8, "uint64" ), + SINGLE ((byte) 7, 4, "single" ), + DOUBLE ((byte) 9, 8, "double" ), + MATRIX ((byte)14, -1, "matrix" ), + COMPRESSED((byte)15, -1, "compressed"), + UTF8 ((byte)16, -1, "utf8" ), + UTF16 ((byte)17, 2, "utf16" ), + UTF32 ((byte)18, 4, "utf32" ); + + private byte _code; + private int _size; + private String _name; + + /** + * Convert a numeric value read from a file into a MLRawType object + */ + static MLRawType byteToMLRawType(byte value) throws MLIOException + { + switch(value) { + case 1: return INT8 ; + case 2: return UINT8 ; + case 3: return INT16 ; + case 4: return UINT16 ; + case 5: return INT32 ; + case 6: return UINT32 ; + case 12: return INT64 ; + case 13: return UINT64 ; + case 7: return SINGLE ; + case 9: return DOUBLE ; + case 14: return MATRIX ; + case 15: return COMPRESSED; + case 16: return UTF8 ; + case 17: return UTF16 ; + case 18: return UTF32 ; + default: + throw new MLIOException("Unknown MLRawType code: " + value); + } + } + + /** + * Constructor + */ + private MLRawType(byte code, int size, String name) + { + _code = code; + _size = size; + _name = name; + } + + /** + * Code used to flag the field type in Matlab .mat files + */ + byte getCode() + { + return _code; + } + + /** + * Size (in bytes) of an element of this type + * @warning Throw an exception for non-atomic types + */ + int getSize() throws MLIOException + { + if(_size<0) { + throw new MLIOException("Cannot determine the size of an element of type " + _name); + } + return _size; + } + + @Override + public String toString() + { + return _name; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLType.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLType.java new file mode 100644 index 0000000000000000000000000000000000000000..efcbcd2b22c9bb74c62ed00aa6dbb0962e43083b --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLType.java @@ -0,0 +1,105 @@ +package plugins.ylemontag.matlabio.lib; + +/** + * + * @author Yoann Le Montagner + * + * Type of a Matlab object (correspond to constants mxFOO_CLASS in the Matlab .mat file reference) + */ +public enum MLType +{ + CELL ((byte) 1, false, null , "cell" ), + STRUCT ((byte) 2, false, null , "struct" ), + OBJECT ((byte) 3, false, null , "object" ), + CHAR ((byte) 4, false, MLRawType.UTF16 , "char" ), + SPARSE ((byte) 5, false, null , "sparse" ), + DOUBLE ((byte) 6, true , MLRawType.DOUBLE, "double" ), + SINGLE ((byte) 7, true , MLRawType.SINGLE, "single" ), + INT8 ((byte) 8, true , MLRawType.INT8 , "int8" ), + UINT8 ((byte) 9, true , MLRawType.UINT8 , "uint8" ), + INT16 ((byte)10, true , MLRawType.INT16 , "int16" ), + UINT16 ((byte)11, true , MLRawType.UINT16, "uint16" ), + INT32 ((byte)12, true , MLRawType.INT32 , "int32" ), + UINT32 ((byte)13, true , MLRawType.UINT32, "uint32" ), + INT64 ((byte)14, true , MLRawType.INT64 , "int64" ), + UINT64 ((byte)15, true , MLRawType.UINT64, "uint64" ), + LOGICAL((byte) 9, false, MLRawType.UINT8 , "logical"); // Not in the Matlab specs + + private byte _code ; + private boolean _isNumeric; // Public + private String _name ; // Public + private MLRawType _rawType ; + + /** + * Convert a numeric value read from a file into a MLClass object + */ + static MLType byteToMLType(byte value) throws MLIOException + { + switch(value) { + case 1: return CELL ; + case 2: return STRUCT; + case 3: return OBJECT; + case 4: return CHAR ; + case 5: return SPARSE; + case 6: return DOUBLE; + case 7: return SINGLE; + case 8: return INT8 ; + case 9: return UINT8 ; + case 10: return INT16 ; + case 11: return UINT16; + case 12: return INT32 ; + case 13: return UINT32; + case 14: return INT64 ; + case 15: return UINT64; + // Never return LOGICAL as this type is not standard + default: + throw new MLIOException("Unknown MLType code: " + value); + } + } + + /** + * Constructor + */ + private MLType(byte code, boolean isNumeric, MLRawType rawType, String name) + { + _code = code ; + _isNumeric = isNumeric; + _name = name ; + _rawType = rawType ; + } + + /** + * Code used to flag the datatype in Matlab .mat files + * @remarks Return the code corresponding to MLType.UINT8 in case of MLType.LOGICAL + */ + byte getCode() + { + return _code; + } + + /** + * Check whether this class is a numeric class + */ + public boolean getIsNumeric() + { + return _isNumeric; + } + + /** + * Canonical raw type corresponding to the current type + * @warning Throw an exception for non-numeric types + */ + MLRawType getRawType() throws MLIOException + { + if(_rawType==null) { + throw new MLIOException("Cannot associate a raw type to type " + _name); + } + return _rawType; + } + + @Override + public String toString() + { + return _name; + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MLUtil.java b/src/main/java/plugins/ylemontag/matlabio/lib/MLUtil.java new file mode 100644 index 0000000000000000000000000000000000000000..483541279ceaf30b4acfbb1bfbcaab3325853549 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MLUtil.java @@ -0,0 +1,324 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * + * @author Yoann Le Montagner + * + * Low-level tools for reading/writing a Matlab .mat file + */ +class MLUtil +{ + /** + * Exception thrown when the end of an input stream is reached + */ + static class EndOfStream extends MLIOException + { + private static final long serialVersionUID = 1L; + + /** + * Constructor + */ + EndOfStream() + { + super("EOF reached unexpectedly"); + } + } + + + + /** + * Header of an element field in a .mat file + */ + static class FieldHeader + { + private MLRawType _type; + private boolean _useShortFormat; + private int _headerLength ; + private int _dataLength ; + private int _paddingLength; + + /** + * Default constructor (use either short format or long format according to + * the length of the data) + */ + FieldHeader(MLRawType type, int dataLength) + { + this(type, dataLength, dataLength<=4); + } + + /** + * Constructor + */ + FieldHeader(MLRawType type, int dataLength, boolean useShortFormat) + { + _type = type; + _useShortFormat = useShortFormat; + _headerLength = useShortFormat ? 4 : 8; + _dataLength = dataLength; + _paddingLength = 8 - ((_headerLength+_dataLength) % 8); + if(_paddingLength==8) { + _paddingLength = 0; + } + } + + /** + * Underlying raw-type + */ + MLRawType getType() + { + return _type; + } + + /** + * Flag indicating whether the field use a short-format encoding or not + */ + boolean getUseShortFormat() + { + return _useShortFormat; + } + + /** + * Length of the header section of the field + */ + int getHeaderLength() + { + return _headerLength; + } + + /** + * Length of the data section of the field + */ + int getDataLength() + { + return _dataLength; + } + + /** + * Length of the padding section at the end of the field + */ + int getPaddingLength() + { + return _paddingLength; + } + + /** + * Total length of the field, including the header, the data and the padding section + */ + int getTotalLength() + { + return _headerLength + _dataLength + _paddingLength; + } + } + + + + /** + * Field of an element in a .mat file + */ + static class Field extends FieldHeader + { + private ByteBuffer _data; + + /** + * Default constructor (use either short format or long format according to + * the length of the data) + */ + Field(MLRawType type, ByteBuffer data) + { + super(type, data.limit()); + _data = data; + } + + /** + * Constructor + */ + Field(FieldHeader header, ByteBuffer data) + { + super(header.getType(), header.getDataLength(), header.getUseShortFormat()); + _data = data; + } + + /** + * Field data + */ + ByteBuffer getData() + { + return _data; + } + } + + + + /** + * Read a field + */ + static Field readField(InputStream stream, ByteOrder endianness) throws IOException + { + FieldHeader header = readFieldHeader(stream, endianness); + ByteBuffer data = consume(stream, endianness, header.getDataLength()); + consume(stream, endianness, header.getPaddingLength()); + return new Field(header, data); + } + + /** + * Write the given field to the stream + */ + static void writeField(OutputStream stream, ByteOrder endianness, Field field) throws IOException + { + writeFieldHeader(stream, endianness, field); + push(stream, field.getData()); + skip(stream, field.getPaddingLength()); + } + + /** + * Read the header of a field + */ + static FieldHeader readFieldHeader(InputStream stream, ByteOrder endianness) throws IOException + { + int rawTypeCode = consume(stream, endianness, 4).getInt(); + MLRawType rawType; + int dataLength; + boolean shortFormat; + if((rawTypeCode & 0xffff0000)==0) { // Long field format + rawType = MLRawType.byteToMLRawType((byte)rawTypeCode); + dataLength = consume(stream, endianness, 4).getInt(); + shortFormat = false; + } + else { // Short field format + rawType = MLRawType.byteToMLRawType((byte)(rawTypeCode & 0x0000ffff)); + dataLength = (rawTypeCode >> 16); + shortFormat = true; + } + if(dataLength<0) { + throw new MLIOException("Negative field length detected"); + } + return new FieldHeader(rawType, dataLength, shortFormat); + } + + /** + * Append the given field header to the stream + */ + static void writeFieldHeader(OutputStream stream, ByteOrder endianness, FieldHeader fieldHeader) + throws IOException + { + ByteBuffer buffer; + if(fieldHeader.getHeaderLength()==4) { // Short field format + int rawType = fieldHeader.getType().getCode(); + rawType |= (fieldHeader.getDataLength() << 16); + buffer = allocate(endianness, 4); + buffer.putInt(rawType); + } + else if(fieldHeader.getHeaderLength()==8) { // Long field format + buffer = allocate(endianness, 8); + buffer.putInt(fieldHeader.getType().getCode()); + buffer.putInt(fieldHeader.getDataLength()); + } + else { + throw new MLIOException("Field header length must be either 4 or 8 byte long"); + } + push(stream, buffer); + } + + /** + * Read 'nb' bytes from the given stream + */ + static ByteBuffer consume(InputStream stream, ByteOrder endianness, int length) throws IOException + { + ByteBuffer retVal = allocate(endianness, length); + consume(stream, retVal, 0, length); + return retVal; + } + + /** + * Read 'nb' bytes from the given stream and put it in the given buffer + */ + static void consume(InputStream stream, ByteBuffer buffer, int offset, int length) throws IOException + { + byte[] rawBuffer = buffer.array(); + int finalOffset = offset + length; + if(finalOffset>rawBuffer.length) { + throw new MLIOException("Unsufficent remaining space in the buffer"); + } + while(offset<finalOffset) { + int currentlyRead = stream.read(rawBuffer, offset, finalOffset - offset); + if(currentlyRead>=0) { + offset += currentlyRead; + } + else { + break; + } + } + if(offset<finalOffset) { + throw new EndOfStream(); + } + } + + /** + * Copy 'length' bytes from in to out + * @throw IOException If either out.remaining()<length or in.remaining()<length + */ + static void copy(ByteBuffer out, int outOffset, ByteBuffer in, int inOffset, int length) throws MLIOException + { + if(length==0) { + return; + } + byte[] rawOut = out.array(); + byte[] rawIn = in .array(); + int finalOutOffset = outOffset + length; + int finalInOffset = inOffset + length; + if(finalOutOffset>rawOut.length) { + throw new MLIOException("Overflow in the output buffer"); + } + if(finalInOffset>rawIn.length) { + throw new MLIOException("Overflow in the input buffer"); + } + for(; outOffset<finalOutOffset; ++outOffset) { + rawOut[outOffset] = rawIn[inOffset]; + ++inOffset; + } + } + + /** + * Allocate a new buffer with the given size and endianness + */ + static ByteBuffer allocate(ByteOrder endianness, int length) + { + byte[] buffer = new byte[length]; + ByteBuffer retVal = ByteBuffer.wrap(buffer); + retVal.order(endianness); + return retVal; + } + + /** + * Write a buffer of bytes in the given stream + */ + static void push(OutputStream stream, ByteBuffer buffer) throws IOException + { + stream.write(buffer.array()); + } + + /** + * Write a buffer of bytes in the given random access file + */ + static void push(RandomAccessFile stream, ByteBuffer buffer) throws IOException + { + stream.write(buffer.array()); + } + + /** + * Write padding data in the given stream + */ + static void skip(OutputStream stream, int length) throws IOException + { + if(length>0) { + stream.write(new byte[length]); + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MatFileReader.java b/src/main/java/plugins/ylemontag/matlabio/lib/MatFileReader.java new file mode 100644 index 0000000000000000000000000000000000000000..3188ac6b5985b63cd86ee0041a275e3fd4118286 --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MatFileReader.java @@ -0,0 +1,656 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.IntBuffer; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.Set; +import java.util.zip.InflaterInputStream; + +/** + * + * @author Yoann Le Montagner + * + * Matlab .mat file reader + */ +public class MatFileReader +{ + /** + * The Matlab variable whose size is less or equal to this parameter will be + * buffered while reading the index of the Matlab file + */ + private static final int SMALL_ELEMENT_SIZE = 0x10000; + + private File _file; + private ByteOrder _endianness; + private Map<String, Element> _index; + + /** + * Element of a .mat file (not decoded) + */ + private static class RawElement + { + public MLRawType type; + public StreamBuilder streamBuilder; + public RawElement(MLRawType srcType, StreamBuilder srcStreamBuilder) + { + type = srcType; + streamBuilder = srcStreamBuilder; + } + } + + /** + * Element of a .mat file (decoded) + */ + private static class Element + { + public MLMeta meta; + public StreamBuilder streamBuilder; + public Element(MLMeta srcMeta, StreamBuilder srcStreamBuilder) + { + meta = srcMeta; + streamBuilder = srcStreamBuilder; + } + } + + /** + * Open a .mat file for reading + */ + public MatFileReader(String path) throws IOException + { + this(new File(path)); + } + + /** + * Open a .mat file for reading + */ + public MatFileReader(File file) throws IOException + { + // Initialize objects + if(file==null) { + throw new MLIOException("No file selected"); + } + _file = file; + _endianness = ByteOrder.BIG_ENDIAN; // Will be changed when reading the file header + _index = new HashMap<String, Element>(); + + // Analyzing the file + FileInputStream mainStream = new FileInputStream(_file); + readHeader(mainStream); + LinkedList<RawElement> rawElems = readRawElements(mainStream); + mainStream.close(); + LinkedList<Element> elems = decodeElements(rawElems); + + // Feeding the index + for(Element elem : elems) { + _index.put(elem.meta.getName(), elem); + } + } + + /** + * Return the underlying file + */ + public File getFile() + { + return _file; + } + + /** + * Return the endianness used for this file + */ + public ByteOrder getEndianness() + { + return _endianness; + } + + /** + * List of the variables contained in the Matlab .mat file + */ + public Set<String> getKeys() + { + return _index.keySet(); + } + + /** + * Return the meta-data associated to a given variable + * @return null if the key does not exist + */ + public MLMeta getMeta(String key) + { + Element elem = _index.get(key); + if(elem==null) { + return null; + } + return elem.meta; + } + + /** + * Return the variable named 'key' + * @throw MLIOException If the given key does not exist + */ + public MLArray getData(String key) throws IOException + { + try { + return getData(key, new Controller()); + } + catch(Controller.CanceledByUser err) { + throw new RuntimeException("A Matlab array import operation have been unexpectedly interrupted"); + } + } + + /** + * Return the variable named 'key' + * @throw MLIOException If the given key does not exist + */ + public MLArray getData(String key, Controller controller) + throws IOException, Controller.CanceledByUser + { + Element elem = _index.get(key); + if(elem==null) { + throw new MLIOException("Cannot find variable named " + key); + } + return retrieveArray(elem, controller); + } + + /** + * Return the variable named 'key' as a stream + * @throw MLIOException If the given key does not exist + */ + public MLIStream getDataAsStream(String key) throws IOException + { + Element elem = _index.get(key); + if(elem==null) { + throw new MLIOException("Cannot find variable named " + key); + } + return retrieveStream(elem); + } + + /** + * Read the header + */ + private void readHeader(FileInputStream stream) throws IOException + { + ByteBuffer buffer = MLUtil.consume(stream, _endianness, 128); + + // Check the description field + if(!(buffer.get()=='M' && buffer.get()=='A' && buffer.get()=='T' && buffer.get()=='L')) { + throw new MLIOException("Not a MATLAB 5.0 MAT-file"); + } + + // Endianness indicator + if(buffer.get(126)=='I' && buffer.get(127)=='M') { + _endianness = ByteOrder.LITTLE_ENDIAN; + } + else if(buffer.get(126)=='M' && buffer.get(127)=='I') { + _endianness = ByteOrder.BIG_ENDIAN; + } + else { + throw new MLIOException("Invalid endianness indicator"); + } + + // Check the version flag + byte versionHi = buffer.get(_endianness==ByteOrder.BIG_ENDIAN ? 124 : 125); + byte versionLo = buffer.get(_endianness==ByteOrder.BIG_ENDIAN ? 125 : 124); + if(!(versionHi==0x01 && versionLo==0x00)) { + throw new MLIOException("Invalid version flag"); + } + } + + /** + * Read the list of elements contained in the file + */ + private LinkedList<RawElement> readRawElements(FileInputStream stream) throws IOException + { + LinkedList<RawElement> retVal = new LinkedList<RawElement>(); + try { + while(true) { + retVal.add(readRawElement(stream)); + } + } + catch(MLUtil.EndOfStream err) {} + return retVal; + } + + /** + * Read an element, i.e. decode its tag + */ + private RawElement readRawElement(FileInputStream stream) throws IOException + { + ByteBuffer buffer = MLUtil.consume(stream, _endianness, 8); + MLRawType type = MLRawType.byteToMLRawType((byte)buffer.getInt()); + int length = buffer.getInt(); + if(length<0) { + throw new MLIOException("Negative segment length detected"); + } + StreamBuilder streamBuilder; + if(length>=SMALL_ELEMENT_SIZE) { + long currentPosition = stream.getChannel().position(); + streamBuilder = new FileInputStreamBuilder(_file, currentPosition); + stream.skip(length); + } + else { + ByteBuffer dataBuffer = MLUtil.consume(stream, _endianness, length); + streamBuilder = new ByteBufferStreamBuilder(dataBuffer); + } + return new RawElement(type, streamBuilder); + } + + /** + * Decode all elements + */ + private LinkedList<Element> decodeElements(LinkedList<RawElement> rawElems) throws IOException + { + LinkedList<Element> retVal = new LinkedList<Element>(); + for(RawElement rawElem : rawElems) { + Element elem = decodeElement(rawElem); + retVal.add(elem); + } + return retVal; + } + + /** + * Decode an element from a raw element + */ + private Element decodeElement(RawElement rawElem) throws IOException + { + switch(rawElem.type) { + case MATRIX: + return decodeMatrixElement(rawElem.streamBuilder); + case COMPRESSED: + return decodeCompressedElement(rawElem.streamBuilder); + default: + throw new MLIOException("Unexpected raw element type: " + rawElem.type); + } + } + + /** + * Decode a compressed element + */ + private Element decodeCompressedElement(StreamBuilder streamBuilder) throws IOException + { + StreamBuilder inflaterStreamBuilder = new InflaterStreamBuilder(streamBuilder); + StreamBuilder nextLevelStreamBuilder = new ShifterStreamBuilder(inflaterStreamBuilder, 8); + ByteBuffer compressedHeader = MLUtil.consume(inflaterStreamBuilder.build(), _endianness, 8); + MLRawType compressedType = MLRawType.byteToMLRawType((byte)compressedHeader.getInt()); + if(compressedType!=MLRawType.MATRIX) { + throw new MLIOException("Unexpected compressed raw element type: " + compressedType); + } + return decodeMatrixElement(nextLevelStreamBuilder); + } + + /** + * Decode a matrix element + */ + private Element decodeMatrixElement(StreamBuilder streamBuilder) throws IOException + { + MLUtil.Field field; + InputStream stream = streamBuilder.build(); + int offset = 0; + + // Array flag sub-element + field = MLUtil.readField(stream, _endianness); + offset += field.getTotalLength(); + if(field.getType()!=MLRawType.UINT32) { + throw new MLIOException("Badly formatted array flag subelement"); + } + int rawFlags = field.getData().getInt(0); + MLType mlClass = MLType.byteToMLType((byte)(rawFlags & 0x000000ff)); + boolean isComplex = (rawFlags & 0x00000800)!=0; + boolean isLogical = (rawFlags & 0x00000200)!=0; + if(isLogical) { + if(mlClass!=MLType.UINT8) { + throw new MLIOException("Badly encoded logical array"); + } + mlClass = MLType.LOGICAL; + } + + // Dimensions sub-element + field = MLUtil.readField(stream, _endianness); + offset += field.getTotalLength(); + if(field.getType()!=MLRawType.INT32) { + throw new MLIOException("Badly formatted dimensions subelement"); + } + IntBuffer rawDims = field.getData().asIntBuffer(); + int nDims = rawDims.limit(); + int[] dims = new int[nDims]; + for(int k=0; k<nDims; ++k) { + dims[k] = rawDims.get(k); + } + + // Name sub-element + field = MLUtil.readField(stream, _endianness); + offset += field.getTotalLength(); + if(field.getType()!=MLRawType.INT8) { + throw new MLIOException("Badly formatted name subelement"); + } + ByteBuffer rawName = field.getData(); + int szName = rawName.limit(); + String name = ""; + for(int k=0; k<szName; ++k) { + name += (char)rawName.get(k); + } + + // Returned value + MLMeta meta = new MLMeta(mlClass, name, dims, isComplex); + Element retVal = new Element(meta, new ShifterStreamBuilder(streamBuilder, offset)); + return retVal; + } + + /** + * Read the content associated to an object in a Matlab .mat file + */ + private MLArray retrieveArray(Element elem, Controller controller) + throws IOException, Controller.CanceledByUser + { + controller.checkPoint(); + MLType type = elem.meta.getType(); + MLArray retVal = null; + switch(type) { + case INT8 : + case UINT8 : + case INT16 : + case UINT16: + case INT32 : + case UINT32: + case INT64 : + case UINT64: + case SINGLE: + case DOUBLE: + retVal = retrieveNumericArray(elem, controller); + break; + case CHAR: + retVal = retrieveCharArray(elem, controller); + break; + case LOGICAL: + retVal = retrieveLogicalArray(elem, controller); + break; + default: + throw new MLIOException("Import of " + type + " objects not implemented yet"); + } + return retVal; + } + + /** + * Read the content associated to a numeric array + */ + private MLArrays.Numeric<?> retrieveNumericArray(Element elem, Controller controller) + throws IOException, Controller.CanceledByUser + { + InputStream stream = elem.streamBuilder.build(); + MLType type = elem.meta.getType(); + boolean isComplex = elem.meta.getIsComplex(); + int elements = elem.meta.getSize(); + if(isComplex) { + elements *= 2; + } + + // Allocate the result + MLArrays.Numeric<?> retVal; + switch(type) { + case INT8 : retVal = new MLArrays.Int8 (elem.meta); break; + case UINT8 : retVal = new MLArrays.UInt8 (elem.meta); break; + case INT16 : retVal = new MLArrays.Int16 (elem.meta); break; + case UINT16: retVal = new MLArrays.UInt16(elem.meta); break; + case INT32 : retVal = new MLArrays.Int32 (elem.meta); break; + case UINT32: retVal = new MLArrays.UInt32(elem.meta); break; + case INT64 : retVal = new MLArrays.Int64 (elem.meta); break; + case UINT64: retVal = new MLArrays.UInt64(elem.meta); break; + case SINGLE: retVal = new MLArrays.Single(elem.meta); break; + case DOUBLE: retVal = new MLArrays.Double(elem.meta); break; + default: + throw new MLIOException("Cannot generate a numeric array from a " + type + " object"); + } + + // Feed the array + controller.startCounter(elements); + MLDataInputStream realPart = new MLDataInputStream(stream, _endianness, type); + feedNumericArray(realPart, retVal, true, controller); + if(isComplex) { + MLDataInputStream imaginaryPart = new MLDataInputStream(stream, _endianness, type); + feedNumericArray(imaginaryPart, retVal, false, controller); + } + controller.stopCounter(); + return retVal; + } + + /** + * Feed a section (either the real part or the imaginary part) of a MLNumericArray + */ + private void feedNumericArray(MLDataInputStream dataStream, MLArrays.Numeric<?> target, boolean isRealPart, + Controller controller) throws IOException, Controller.CanceledByUser + { + if(isRealPart) { + switch(target.getType()) { + case INT8 : dataStream.consumeInt8 (((MLArrays.Int8 )target).getReal(), controller); break; + case UINT8 : dataStream.consumeUInt8 (((MLArrays.UInt8 )target).getReal(), controller); break; + case INT16 : dataStream.consumeInt16 (((MLArrays.Int16 )target).getReal(), controller); break; + case UINT16: dataStream.consumeUInt16(((MLArrays.UInt16)target).getReal(), controller); break; + case INT32 : dataStream.consumeInt32 (((MLArrays.Int32 )target).getReal(), controller); break; + case UINT32: dataStream.consumeUInt32(((MLArrays.UInt32)target).getReal(), controller); break; + case INT64 : dataStream.consumeInt64 (((MLArrays.Int64 )target).getReal(), controller); break; + case UINT64: dataStream.consumeUInt64(((MLArrays.UInt64)target).getReal(), controller); break; + case SINGLE: dataStream.consumeSingle(((MLArrays.Single)target).getReal(), controller); break; + case DOUBLE: dataStream.consumeDouble(((MLArrays.Double)target).getReal(), controller); break; + default: + throw new MLIOException("Cannot generate a numeric array from a " + target.getType() + " object"); + } + } + else { + switch(target.getType()) { + case INT8 : dataStream.consumeInt8 (((MLArrays.Int8 )target).getImaginary(), controller); break; + case UINT8 : dataStream.consumeUInt8 (((MLArrays.UInt8 )target).getImaginary(), controller); break; + case INT16 : dataStream.consumeInt16 (((MLArrays.Int16 )target).getImaginary(), controller); break; + case UINT16: dataStream.consumeUInt16(((MLArrays.UInt16)target).getImaginary(), controller); break; + case INT32 : dataStream.consumeInt32 (((MLArrays.Int32 )target).getImaginary(), controller); break; + case UINT32: dataStream.consumeUInt32(((MLArrays.UInt32)target).getImaginary(), controller); break; + case INT64 : dataStream.consumeInt64 (((MLArrays.Int64 )target).getImaginary(), controller); break; + case UINT64: dataStream.consumeUInt64(((MLArrays.UInt64)target).getImaginary(), controller); break; + case SINGLE: dataStream.consumeSingle(((MLArrays.Single)target).getImaginary(), controller); break; + case DOUBLE: dataStream.consumeDouble(((MLArrays.Double)target).getImaginary(), controller); break; + default: + throw new MLIOException("Cannot generate a numeric array from a " + target.getType() + " object"); + } + } + } + + /** + * Read the content associated to a char array + */ + private MLArrays.Char retrieveCharArray(Element elem, Controller controller) + throws IOException, Controller.CanceledByUser + { + InputStream stream = elem.streamBuilder.build(); + MLDataInputStream dataStream = new MLDataInputStream(stream, _endianness, MLType.CHAR); + MLArrays.Char retVal = new MLArrays.Char(elem.meta); + controller.startCounter(elem.meta.getSize()); + dataStream.consumeChar(retVal.get(), controller); + controller.stopCounter(); + return retVal; + } + + /** + * Read the content associated to a char array + */ + private MLArrays.Logical retrieveLogicalArray(Element elem, Controller controller) + throws IOException, Controller.CanceledByUser + { + InputStream stream = elem.streamBuilder.build(); + MLDataInputStream dataStream = new MLDataInputStream(stream, _endianness, MLType.LOGICAL); + MLArrays.Logical retVal = new MLArrays.Logical(elem.meta); + controller.startCounter(elem.meta.getSize()); + dataStream.consumeLogical(retVal.get(), controller); + controller.stopCounter(); + return retVal; + } + + /** + * Prepare the stream to read an object in a Matlab .mat file + */ + private MLIStream retrieveStream(Element elem) throws IOException + { + MLType type = elem.meta.getType(); + MLIStream retVal = null; + switch(type) { + case LOGICAL: /// TODO: MLIStreams.Logical should not inherit from MLIStreams.Numeric + case INT8 : + case UINT8 : + case INT16 : + case UINT16: + case INT32 : + case UINT32: + case INT64 : + case UINT64: + case SINGLE: + case DOUBLE: + retVal = retrieveNumericStream(elem); + break; + default: + throw new MLIOException("Import of " + type + " objects not implemented yet"); + } + return retVal; + } + + /** + * Prepare the stream associated to a numeric array + */ + private MLIStreams.Numeric retrieveNumericStream(Element elem) throws IOException + { + MLType type = elem.meta.getType(); + InputStream rawStream = elem.streamBuilder.build(); + switch(type) { + case LOGICAL: return new MLIStreams.Logical(elem.meta, rawStream, _endianness); //TODO: remove from retrieveNumericStream + case INT8 : return new MLIStreams.Int8 (elem.meta, rawStream, _endianness); + case UINT8 : return new MLIStreams.UInt8 (elem.meta, rawStream, _endianness); + case INT16 : return new MLIStreams.Int16 (elem.meta, rawStream, _endianness); + case UINT16: return new MLIStreams.UInt16(elem.meta, rawStream, _endianness); + case INT32 : return new MLIStreams.Int32 (elem.meta, rawStream, _endianness); + case UINT32: return new MLIStreams.UInt32(elem.meta, rawStream, _endianness); + case INT64 : return new MLIStreams.Int64 (elem.meta, rawStream, _endianness); + case UINT64: return new MLIStreams.UInt64(elem.meta, rawStream, _endianness); + case SINGLE: return new MLIStreams.Single(elem.meta, rawStream, _endianness); + case DOUBLE: return new MLIStreams.Double(elem.meta, rawStream, _endianness); + default: + throw new MLIOException("Import of " + type + " objects not implemented yet"); + } + } + + /** + * Stream builder interface, that is used to build one stream for each raw element + */ + private static interface StreamBuilder + { + /** + * Open a new stream + */ + public InputStream build() throws IOException; + } + + /** + * Stream builder for a ByteBuffer-based stream + */ + private static class ByteBufferStreamBuilder implements StreamBuilder + { + private ByteBuffer _data; + + /** + * Constructor + */ + public ByteBufferStreamBuilder(ByteBuffer data) + { + _data = data; + } + + @Override + public InputStream build() throws IOException + { + return new ByteArrayInputStream(_data.array()); + } + } + + /** + * Stream builder for a FileInputStream-based stream + */ + private static class FileInputStreamBuilder implements StreamBuilder + { + private File _file; + private long _offset; + + /** + * Constructor + */ + public FileInputStreamBuilder(File file, long offset) + { + _file = file; + _offset = offset; + } + + @Override + public InputStream build() throws IOException + { + try { + FileInputStream retVal = new FileInputStream(_file); + retVal.skip(_offset); + return retVal; + } + catch(FileNotFoundException err) { + throw new MLIOException("Unable to open a new channel on the current file"); + } + } + } + + /** + * Inflater stream wrapper + */ + private static class InflaterStreamBuilder implements StreamBuilder + { + private StreamBuilder _in; + + /** + * Constructor + */ + public InflaterStreamBuilder(StreamBuilder in) + { + _in = in; + } + + @Override + public InputStream build() throws IOException + { + return new InflaterInputStream(_in.build()); + } + } + + /** + * Shifter stream wrapper + */ + private static class ShifterStreamBuilder implements StreamBuilder + { + private StreamBuilder _in; + private int _offset; + + /** + * Constructor + */ + public ShifterStreamBuilder(StreamBuilder in, int offset) + { + _in = in; + _offset = offset; + } + + @Override + public InputStream build() throws IOException + { + InputStream retVal = _in.build(); + retVal.skip(_offset); + return retVal; + } + } +} diff --git a/src/main/java/plugins/ylemontag/matlabio/lib/MatFileWriter.java b/src/main/java/plugins/ylemontag/matlabio/lib/MatFileWriter.java new file mode 100644 index 0000000000000000000000000000000000000000..f304eaabc995ad20690269e59c583cde68fdd72b --- /dev/null +++ b/src/main/java/plugins/ylemontag/matlabio/lib/MatFileWriter.java @@ -0,0 +1,549 @@ +package plugins.ylemontag.matlabio.lib; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.zip.DeflaterOutputStream; + +import plugins.ylemontag.matlabio.lib.Controller.CanceledByUser; + +/** + * + * @author Yoann Le Montagner + * + * Matlab .mat file writer + */ +public class MatFileWriter +{ + private File _file; + private boolean _useCompression; + private ByteOrder _endianness; + private RandomAccessFile _mainStream; + private SectionValidator _currentValidator; + private WeakReference<SectionOutputStream> _currentSection; + private Map<String, MLMeta> _index; + + /** + * Open a .mat file for writing + */ + public MatFileWriter(String path, boolean append) throws IOException + { + this(new File(path), append); + } + + /** + * Open a .mat file for writing + */ + public MatFileWriter(File file, boolean append) throws IOException + { + // Initialize objects + if(file==null) { + throw new MLIOException("No file selected"); + } + _file = file; + _useCompression = false; + _index = new HashMap<String, MLMeta>(); + + // Try reading the existing file in case of append==true + if(append) { + MatFileReader reader = new MatFileReader(file); + _endianness = reader.getEndianness(); + for(String key : reader.getKeys()) { + _index.put(key, reader.getMeta(key)); + } + } + else { + _endianness = ByteOrder.LITTLE_ENDIAN; + } + + // Create the output stream + _mainStream = new RandomAccessFile(_file, "rw"); + if(append) { + _mainStream.seek(_mainStream.length()); + } + else { + _mainStream.setLength(0); + writeHeader(); + } + } + + /** + * Return the underlying file + */ + public File getFile() + { + return _file; + } + + /** + * Return the endianness used for this file + */ + public ByteOrder getEndianness() + { + return _endianness; + } + + /** + * List of the variables contained in the Matlab .mat file + */ + public Set<String> getKeys() + { + return _index.keySet(); + } + + /** + * Return the meta-data associated to a given variable + * @return null if the key does not exist + */ + public MLMeta getMeta(String key) + { + return _index.get(key); + } + + /** + * Append an array to the end of the file + */ + public void putData(MLArray array) throws IOException + { + try { + putData(array, new Controller()); + } + catch(CanceledByUser err) { + throw new MLIOException("A Matlab array export operation have been unexpectedly interrupted"); + } + } + + /** + * Append an array to the end of the file + */ + public void putData(MLArray array, Controller controller) throws IOException, Controller.CanceledByUser + { + OutputStream rawStream = allocateStream(array.getMeta()); + processArray(rawStream, array, controller); + rawStream.close(); + } + + /** + * Open a stream to write data and the end of the file + */ + public MLOStream putDataAsStream(MLMeta meta) throws IOException + { + OutputStream rawStream = allocateStream(meta); + return processStream(rawStream, meta); + } + + /** + * Writing the file header + */ + private void writeHeader() throws IOException + { + ByteBuffer buffer = MLUtil.allocate(_endianness, 128); + + // Write the description field + String description = "MATLAB 5.0 MAT-file, Created by: MatlabIO java library"; + for(char c : description.toCharArray()) { + buffer.put((byte)c); + } + for(int k=description.length(); k<124; ++k) { + buffer.put((byte)' '); + } + + // Endianness indicator and version flag + if(_endianness==ByteOrder.LITTLE_ENDIAN) { + buffer.put(124, (byte)0x00); + buffer.put(125, (byte)0x01); + buffer.put(126, (byte)'I'); + buffer.put(127, (byte)'M'); + } + else if(_endianness==ByteOrder.BIG_ENDIAN) { + buffer.put(124, (byte)0x01); + buffer.put(125, (byte)0x00); + buffer.put(126, (byte)'M'); + buffer.put(127, (byte)'I'); + } + else { + throw new MLIOException("Unknown endianness flag: " + _endianness); + } + + // Actually write the file header + MLUtil.push(_mainStream, buffer); + } + + /** + * Allocate a new stream to write a new element + */ + private OutputStream allocateStream(MLMeta meta) throws IOException + { + // Ensure that no section is beeing written + ensurePreviousSectionClosed(); + + // Check that the name of the new element is not already used in the file + if(_index.containsKey(meta.getName())) { + throw new MLIOException( + "The key '" + meta.getName() + "' is already in used in " + _file.getName() + ); + } + + // Allocate the new section validator and output stream + _currentValidator = new SectionValidator(meta); + SectionOutputStream currentSection = new SectionOutputStream(_mainStream, _currentValidator); + _currentSection = new WeakReference<SectionOutputStream>(currentSection); + + // With the compressed option + if(_useCompression) { + OutputStream retVal = new DeflaterOutputStream(currentSection); + ByteBuffer compressedHeader = MLUtil.allocate(_endianness, 8); + compressedHeader.putInt(MLRawType.MATRIX.getCode()); + compressedHeader.putInt(0); // This would lead to errors, therefore compression should not be used + MLUtil.push(retVal, compressedHeader); + return retVal; + } + + // Without the compressed option + else { + return currentSection; + } + } + + @Override + protected void finalize() throws Throwable + { + ensurePreviousSectionClosed(); + } + + /** + * Ensure that the previously written section either has been properly closed + * or has been interrupted + */ + private void ensurePreviousSectionClosed() throws IOException + { + if(_currentValidator==null || _currentValidator.isClosed()) { + return; + } + + // Try to release the weak reference pointing on the output stream used to + // write the current section + System.gc(); + + // If the weak reference cannot be released, this means that a write operation + // is in progress + if(_currentSection!=null && _currentSection.get()!=null) { + throw new MLIOException("There is still a write operation in progress"); + } + + // Otherwise, this means that the previous write operation have been canceled, + // so force the section to be closed + else { + _currentValidator.safeClose(); + _currentValidator = null; + _currentSection = null; + } + } + + /** + * Write the meta-data associated to an array in the given stream + */ + private void processMeta(OutputStream stream, MLMeta meta) throws IOException + { + ByteBuffer buffer; + + // Array flags sub-element + int rawFlags = meta.getType().getCode(); + if(meta.getType()==MLType.LOGICAL) { + rawFlags |= 0x00000200; + } + if(meta.getIsComplex()) { + rawFlags |= 0x00000800; + } + buffer = MLUtil.allocate(_endianness, 8); + buffer.putInt(rawFlags); + MLUtil.writeField(stream, _endianness, new MLUtil.Field(MLRawType.UINT32, buffer)); + + // Dimensions sub-element + int[] dims = meta.getDimensions(); + buffer = MLUtil.allocate(_endianness, 4*dims.length); + for(int k=0; k<dims.length; ++k) { + buffer.putInt(dims[k]); + } + MLUtil.writeField(stream, _endianness, new MLUtil.Field(MLRawType.INT32, buffer)); + + // Name sub-element + String name = meta.getName(); + buffer = MLUtil.allocate(_endianness, name.length()); + for(int k=0; k<name.length(); ++k) { + buffer.put((byte)name.charAt(k)); + } + MLUtil.writeField(stream, _endianness, new MLUtil.Field(MLRawType.INT8, buffer)); + } + + /** + * Write an array in the given stream + */ + private void processArray(OutputStream stream, MLArray array, Controller controller) + throws IOException, Controller.CanceledByUser + { + processMeta(stream, array.getMeta()); + MLType type = array.getType(); + switch(type) { + case INT8 : + case UINT8 : + case INT16 : + case UINT16: + case INT32 : + case UINT32: + case INT64 : + case UINT64: + case SINGLE: + case DOUBLE: + processNumericArray(stream, (MLArrays.Numeric<?>)array, controller); + break; + case CHAR: + processCharArray(stream, (MLArrays.Char)array, controller); + break; + case LOGICAL: + processLogicalArray(stream, (MLArrays.Logical)array, controller); + break; + default: + throw new MLIOException("Export of " + type + " objects not implemented yet"); + } + } + + /** + * Write the content of a numeric array in the given stream + */ + private void processNumericArray(OutputStream stream, MLArrays.Numeric<?> array, Controller controller) + throws IOException, Controller.CanceledByUser + { + MLType type = array.getType(); + boolean isComplex = array.getIsComplex(); + int elements = array.getSize(); + + // Flush the data + controller.startCounter(isComplex ? elements*2 : elements); + MLDataOutputStream realPart = new MLDataOutputStream(stream, _endianness, type, elements); + flushNumericArray(realPart, array, true, controller); + if(isComplex) { + MLDataOutputStream imaginaryPart = new MLDataOutputStream(stream, _endianness, type, elements); + flushNumericArray(imaginaryPart, array, false, controller); + } + controller.stopCounter(); + } + + /** + * Flush the content of a MLArray into a MLDataOutputStream + */ + private void flushNumericArray(MLDataOutputStream dataStream, MLArrays.Numeric<?> source, boolean isRealPart, + Controller controller) throws IOException, Controller.CanceledByUser + { + if(isRealPart) { + switch(source.getType()) { + case INT8 : dataStream.pushInt8 (((MLArrays.Int8 )source).getReal(), controller); break; + case UINT8 : dataStream.pushUInt8 (((MLArrays.UInt8 )source).getReal(), controller); break; + case INT16 : dataStream.pushInt16 (((MLArrays.Int16 )source).getReal(), controller); break; + case UINT16: dataStream.pushUInt16(((MLArrays.UInt16)source).getReal(), controller); break; + case INT32 : dataStream.pushInt32 (((MLArrays.Int32 )source).getReal(), controller); break; + case UINT32: dataStream.pushUInt32(((MLArrays.UInt32)source).getReal(), controller); break; + case INT64 : dataStream.pushInt64 (((MLArrays.Int64 )source).getReal(), controller); break; + case UINT64: dataStream.pushUInt64(((MLArrays.UInt64)source).getReal(), controller); break; + case SINGLE: dataStream.pushSingle(((MLArrays.Single)source).getReal(), controller); break; + case DOUBLE: dataStream.pushDouble(((MLArrays.Double)source).getReal(), controller); break; + default: + throw new MLIOException("Cannot process a numeric array with a " + source.getType() + " type"); + } + } + else { + switch(source.getType()) { + case INT8 : dataStream.pushInt8 (((MLArrays.Int8 )source).getImaginary(), controller); break; + case UINT8 : dataStream.pushUInt8 (((MLArrays.UInt8 )source).getImaginary(), controller); break; + case INT16 : dataStream.pushInt16 (((MLArrays.Int16 )source).getImaginary(), controller); break; + case UINT16: dataStream.pushUInt16(((MLArrays.UInt16)source).getImaginary(), controller); break; + case INT32 : dataStream.pushInt32 (((MLArrays.Int32 )source).getImaginary(), controller); break; + case UINT32: dataStream.pushUInt32(((MLArrays.UInt32)source).getImaginary(), controller); break; + case INT64 : dataStream.pushInt64 (((MLArrays.Int64 )source).getImaginary(), controller); break; + case UINT64: dataStream.pushUInt64(((MLArrays.UInt64)source).getImaginary(), controller); break; + case SINGLE: dataStream.pushSingle(((MLArrays.Single)source).getImaginary(), controller); break; + case DOUBLE: dataStream.pushDouble(((MLArrays.Double)source).getImaginary(), controller); break; + default: + throw new MLIOException("Cannot process a numeric array with a " + source.getType() + " type"); + } + } + } + + /** + * Write the content of a char array in the given stream + */ + private void processCharArray(OutputStream stream, MLArrays.Char array, Controller controller) + throws IOException, Controller.CanceledByUser + { + int elements = array.getSize(); + MLDataOutputStream dataStream = new MLDataOutputStream(stream, _endianness, MLType.CHAR, elements); + controller.startCounter(elements); + dataStream.pushChar(array.get(), controller); + controller.stopCounter(); + } + + /** + * Write the content of a boolean array in the given stream + */ + private void processLogicalArray(OutputStream stream, MLArrays.Logical array, Controller controller) + throws IOException, Controller.CanceledByUser + { + int elements = array.getSize(); + MLDataOutputStream dataStream = new MLDataOutputStream(stream, _endianness, MLType.LOGICAL, elements); + controller.startCounter(elements); + dataStream.pushLogical(array.get(), controller); + controller.stopCounter(); + } + + /** + * Prepare a stream object to write at the end of the current file + */ + private MLOStream processStream(OutputStream stream, MLMeta meta) throws IOException + { + processMeta(stream, meta); + MLType type = meta.getType(); + switch(type) { + case INT8 : return new MLOStreams.Int8 (meta, stream, _endianness); + case UINT8 : return new MLOStreams.UInt8 (meta, stream, _endianness); + case INT16 : return new MLOStreams.Int16 (meta, stream, _endianness); + case UINT16: return new MLOStreams.UInt16(meta, stream, _endianness); + case INT32 : return new MLOStreams.Int32 (meta, stream, _endianness); + case UINT32: return new MLOStreams.UInt32(meta, stream, _endianness); + case INT64 : return new MLOStreams.Int64 (meta, stream, _endianness); + case UINT64: return new MLOStreams.UInt64(meta, stream, _endianness); + case SINGLE: return new MLOStreams.Single(meta, stream, _endianness); + case DOUBLE: return new MLOStreams.Double(meta, stream, _endianness); + default: + throw new MLIOException("Export of " + type + " objects not implemented yet"); + } + } + + /** + * Hold the necessary information to close a section in a Matlab .mat file in + * a proper way + */ + private class SectionValidator + { + private MLMeta _meta; + private long _initialPosition; + private boolean _validated; + private boolean _closed; + + /** + * Constructor + */ + public SectionValidator(MLMeta meta) throws IOException + { + _meta = meta; + _initialPosition = _mainStream.getFilePointer(); + _validated = false; + _closed = false; + _mainStream.seek(_initialPosition + 8); + } + + /** + * Check if the section has been closed + */ + public boolean isClosed() + { + return _closed; + } + + /** + * Mark the section as valid, and close it + */ + public void validateAndClose() throws IOException + { + _validated = true; + safeClose(); + } + + /** + * Close the section, i.e. report its length if it has been marked as valid, + * or cut the file at its begining if it has not + */ + public void safeClose() throws IOException + { + if(_closed) { + return; + } + + // Report the length and type of the section at its begining if it has + // been marked as valid + if(_validated) + { + // Determinate the size of the current element in the file + long currentPosition = _mainStream.getFilePointer(); + int actualElementSize = (int)(currentPosition - _initialPosition) - 8; + ByteBuffer elementHeader = MLUtil.allocate(_endianness, 8); + elementHeader.putInt(_useCompression ? MLRawType.COMPRESSED.getCode() : MLRawType.MATRIX.getCode()); + elementHeader.putInt(actualElementSize); + + // Write it at its beginning and go back to the current position + _mainStream.seek(_initialPosition); + MLUtil.push(_mainStream, elementHeader); + _mainStream.seek(currentPosition); + + // Register the variable just written in the local index + _index.put(_meta.getName(), _meta); + } + + // Otherwise, cut the file + else { + _mainStream.setLength(_initialPosition); + } + + // Now, the section is closed + _closed = true; + } + } + + /** + * Useful class to keep track of the number of written bytes + */ + private static class SectionOutputStream extends OutputStream + { + private RandomAccessFile _out; + private SectionValidator _validator; + + /** + * Constructor + */ + public SectionOutputStream(RandomAccessFile out, SectionValidator validator) + { + _out = out; + _validator = validator; + } + + @Override + public void finalize() throws Throwable {} + + @Override + public void close() throws IOException + { + if(_validator!=null) { + _validator.validateAndClose(); + _validator = null; + } + } + + @Override + public void write(byte[] b, int off, int len) throws IOException + { + _out.write(b, off, len); + } + + @Override + public void write(int b) throws IOException + { + _out.write(b); + } + } +} diff --git a/src/main/resources/icon-Matlab-function-caller.png b/src/main/resources/icon-Matlab-function-caller.png new file mode 100644 index 0000000000000000000000000000000000000000..d66688871bd6a31c7e2aaaf1fe366264097df00d Binary files /dev/null and b/src/main/resources/icon-Matlab-function-caller.png differ diff --git a/src/main/resources/tooltip-Matlab-function-caller.png b/src/main/resources/tooltip-Matlab-function-caller.png new file mode 100644 index 0000000000000000000000000000000000000000..bfcfb9de757eeb8e787ae894aeb0b567fda8ac93 Binary files /dev/null and b/src/main/resources/tooltip-Matlab-function-caller.png differ