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. &lt;<http://fsf.org/>&gt;_
+
+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
+&lt;<http://www.gnu.org/licenses/>&gt;.
+
+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
+&lt;<http://www.gnu.org/philosophy/why-not-lgpl.html>&gt;.
\ 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