From 28ff9891ac52a7d516a2ec43f89b8019151046ad Mon Sep 17 00:00:00 2001
From: Stephane Dallongeville <stephane.dallongeville@pasteur.fr>
Date: Thu, 21 Jul 2022 16:25:29 +0200
Subject: [PATCH] Initial commit

---
 LICENSE.md                                    |  595 ++++++++++
 pom.xml                                       |   81 ++
 .../ylemontag/matlabio/ComplexMode.java       |   52 +
 .../plugins/ylemontag/matlabio/Cursor.java    |  414 +++++++
 .../plugins/ylemontag/matlabio/Cursors.java   |  629 +++++++++++
 .../ylemontag/matlabio/DimensionMapping.java  |  216 ++++
 .../ylemontag/matlabio/IcyDimension.java      |   42 +
 .../ylemontag/matlabio/MatlabExporter.java    |  492 ++++++++
 .../ylemontag/matlabio/MatlabIOLibrary.java   |   15 +
 .../ylemontag/matlabio/MatlabImporter.java    |  337 ++++++
 .../matlabio/gui/ComplexModeComponent.java    |   52 +
 .../gui/DimensionMappingComponent.java        |  265 +++++
 .../matlabio/gui/FileChooserComponent.java    |  147 +++
 .../matlabio/gui/IcyDimensionComponent.java   |   55 +
 .../matlabio/gui/MatlabProgressFrame.java     |   46 +
 .../matlabio/gui/SequenceButton.java          |  151 +++
 .../ylemontag/matlabio/lib/Controller.java    |  135 +++
 .../ylemontag/matlabio/lib/MLArray.java       |  169 +++
 .../ylemontag/matlabio/lib/MLArrays.java      |  341 ++++++
 .../matlabio/lib/MLDataInputStream.java       | 1004 +++++++++++++++++
 .../matlabio/lib/MLDataOutputStream.java      |  531 +++++++++
 .../ylemontag/matlabio/lib/MLIOException.java |   30 +
 .../ylemontag/matlabio/lib/MLIStream.java     |   35 +
 .../ylemontag/matlabio/lib/MLIStreams.java    |  488 ++++++++
 .../ylemontag/matlabio/lib/MLMeta.java        |  117 ++
 .../ylemontag/matlabio/lib/MLOStream.java     |   34 +
 .../ylemontag/matlabio/lib/MLOStreams.java    |  453 ++++++++
 .../ylemontag/matlabio/lib/MLObject.java      |   70 ++
 .../ylemontag/matlabio/lib/MLRawType.java     |   92 ++
 .../ylemontag/matlabio/lib/MLType.java        |  105 ++
 .../ylemontag/matlabio/lib/MLUtil.java        |  324 ++++++
 .../ylemontag/matlabio/lib/MatFileReader.java |  656 +++++++++++
 .../ylemontag/matlabio/lib/MatFileWriter.java |  549 +++++++++
 .../resources/icon-Matlab-function-caller.png |  Bin 0 -> 4052 bytes
 .../tooltip-Matlab-function-caller.png        |  Bin 0 -> 21881 bytes
 35 files changed, 8722 insertions(+)
 create mode 100644 LICENSE.md
 create mode 100644 pom.xml
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/ComplexMode.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/Cursor.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/Cursors.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/DimensionMapping.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/IcyDimension.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/MatlabExporter.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/MatlabIOLibrary.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/MatlabImporter.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/ComplexModeComponent.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/DimensionMappingComponent.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/FileChooserComponent.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/IcyDimensionComponent.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/MatlabProgressFrame.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/gui/SequenceButton.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/Controller.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLArray.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLArrays.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLDataInputStream.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLDataOutputStream.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLIOException.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLIStream.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLIStreams.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLMeta.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLOStream.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLOStreams.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLObject.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLRawType.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLType.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MLUtil.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MatFileReader.java
 create mode 100644 src/main/java/plugins/ylemontag/matlabio/lib/MatFileWriter.java
 create mode 100644 src/main/resources/icon-Matlab-function-caller.png
 create mode 100644 src/main/resources/tooltip-Matlab-function-caller.png

diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..bf09192
--- /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 0000000..7f2e77c
--- /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 0000000..fa40464
--- /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 0000000..cf36086
--- /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 0000000..1bfe875
--- /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 0000000..fcc7e4c
--- /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 0000000..749456c
--- /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 0000000..409c68c
--- /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 0000000..74b5628
--- /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 0000000..70f393d
--- /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 0000000..974a066
--- /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 0000000..d13dd09
--- /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 0000000..667b50a
--- /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 0000000..0de8f37
--- /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 0000000..283ec22
--- /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 0000000..1f8c73f
--- /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 0000000..e797b68
--- /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 0000000..c68402b
--- /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 0000000..5d84199
--- /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 0000000..668fcaa
--- /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 0000000..d6cc7f0
--- /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 0000000..130375c
--- /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 0000000..1de0d50
--- /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 0000000..1b8e6df
--- /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 0000000..d30703a
--- /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 0000000..72c05e0
--- /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 0000000..6f03008
--- /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 0000000..11d7e82
--- /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 0000000..6f71152
--- /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 0000000..efcbcd2
--- /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 0000000..4835412
--- /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 0000000..3188ac6
--- /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 0000000..f304eaa
--- /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
GIT binary patch
literal 4052
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
GLSTY({mohc

literal 0
HcmV?d00001

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
GIT binary patch
literal 21881
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

literal 0
HcmV?d00001

-- 
GitLab