diff --git a/.gitignore b/.gitignore index a7f5acc..471dec8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -*.o -*.moc* -Makefile -build +neuroscope/neuroscope.pro.user +neuroscope/neuroscope.pro.user.4.9-pre1 + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9bd56be --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,339 @@ +GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + +Copyright (C) 1989, 1991 Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + + Preamble + +The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + +For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + +We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + +Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + +Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + +The precise terms and conditions for copying, distribution and +modification follow. + +GNU GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + +1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +a) You must cause the modified files to carry prominent notices +stating that you changed the files and the date of any change. + +b) You must cause any work that you distribute or publish, that in +whole or in part contains or is derived from the Program or any +part thereof, to be licensed as a whole at no charge to all third +parties under the terms of this License. + +c) If the modified program normally reads commands interactively +when run, you must cause it, when started running for such +interactive use in the most ordinary way, to print or display an +announcement including an appropriate copyright notice and a +notice that there is no warranty (or else, saying that you provide +a warranty) and that users may redistribute the program under +these conditions, and telling the user how to view a copy of this +License. (Exception: if the Program itself is interactive but +does not normally print such an announcement, your work based on +the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + +a) Accompany it with the complete corresponding machine-readable +source code, which must be distributed under the terms of Sections +1 and 2 above on a medium customarily used for software interchange; or, + +b) Accompany it with a written offer, valid for at least three +years, to give any third party, for a charge no more than your +cost of physically performing source distribution, a complete +machine-readable copy of the corresponding source code, to be +distributed under the terms of Sections 1 and 2 above on a medium +customarily used for software interchange; or, + +c) Accompany it with the information you received as to the offer +to distribute corresponding source code. (This alternative is +allowed only for noncommercial distribution and only if you +received the program in object code or executable form with such +an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + +4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + +5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + +6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + +7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + +9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + +10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + +11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + +12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + +Copyright (C) + +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 2 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, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + +Gnomovision version 69, Copyright (C) year name of author +Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the program +`Gnomovision' (which makes passes at compilers) written by James Hacker. + +, 1 April 1989 +Ty Coon, President of Vice + +This 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. diff --git a/extra.txt b/extra.txt new file mode 100644 index 0000000..7f59232 --- /dev/null +++ b/extra.txt @@ -0,0 +1 @@ +extra file diff --git a/neuroscope/neuroscope.pro b/neuroscope/neuroscope.pro new file mode 100644 index 0000000..6baf369 --- /dev/null +++ b/neuroscope/neuroscope.pro @@ -0,0 +1,333 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-08-11T11:54:29 +# +#------------------------------------------------- + +QT += core gui widgets xml printsupport + + +TARGET = neuroscope +TEMPLATE = app + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS +//DEFINES += NEUROSCOPE_VERSION=2.1 +//DEFINES += NEUROSCOPE_DOC_PATH="" + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +CONFIG += c++11 + + +INCLUDEPATH += ../../libneurosuite/src/gui +INCLUDEPATH += ../../libneurosuite/src/shared +INCLUDEPATH += ../../libneurosuite/src +INCLUDEPATH += ../../libneurosuite/src/gui/page + +INCLUDEPATH += ../../HDF5/include + + +SOURCES += \ + ../src/baseframe.cpp \ + ../src/cerebusclustersprovider.cpp \ + ../src/cerebuseventsprovider.cpp \ + ../src/cerebustraceprovider.cpp \ + ../src/channelgroupview.cpp \ + ../src/channeliconview.cpp \ + ../src/channelmimedata.cpp \ + ../src/channelpalette.cpp \ + ../src/clustercolors.cpp \ + ../src/clusterproperties.cpp \ + ../src/clusterpropertieslayout.cpp \ + ../src/clustersprovider.cpp \ + ../src/configuration.cpp \ + ../src/dataprovider.cpp \ + ../src/eventdata.cpp \ + ../src/eventsprovider.cpp \ + ../src/globaleventsprovider.cpp \ + ../src/imagecreator.cpp \ + ../src/itemgroupview.cpp \ + ../src/itemiconview.cpp \ + ../src/itempalette.cpp \ + ../src/main.cpp \ + ../src/neuroscope.cpp \ + ../src/neuroscopedoc.cpp \ + ../src/neuroscopeview.cpp \ + ../src/neuroscopexmlreader.cpp \ + ../src/nevclustersprovider.cpp \ + ../src/neveventsprovider.cpp \ + ../src/nsxtracesprovider.cpp \ + ../src/parameterxmlcreator.cpp \ + ../src/parameterxmlmodifier.cpp \ + ../src/positionproperties.cpp \ + ../src/positionpropertieslayout.cpp \ + ../src/positionsprovider.cpp \ + ../src/positionview.cpp \ + ../src/prefdefaults.cpp \ + ../src/prefdefaultslayout.cpp \ + ../src/prefdialog.cpp \ + ../src/prefgeneral.cpp \ + ../src/prefgenerallayout.cpp \ + ../src/properties.cpp \ + ../src/propertiesdialog.cpp \ + ../src/propertieslayout.cpp \ + ../src/sessionxmlwriter.cpp \ + ../src/tags.cpp \ + ../src/tracesprovider.cpp \ + ../src/traceview.cpp \ + ../src/tracewidget.cpp \ + ../src/ReadHDF5.cpp \ + ../src/hdf5utilities.cpp \ + ../src/nwblocations.cpp \ + ../src/nwbreader.cpp \ + ../src/nwbtracesprovider.cpp \ + ../src/nwbeventsprovider.cpp \ + ../src/nwbclustersprovider.cpp + +HEADERS += \ + ../src/baseframe.h \ + ../src/blackrock.h \ + ../src/cerebusclustersprovider.h \ + ../src/cerebuseventsprovider.h \ + ../src/cerebustraceprovider.h \ + ../src/channelgroupview.h \ + ../src/channeliconview.h \ + ../src/channelmimedata.h \ + ../src/channelpalette.h \ + ../src/clustercolors.h \ + ../src/clusterproperties.h \ + ../src/clusterpropertieslayout.h \ + ../src/clustersprovider.h \ + ../src/configuration.h \ + ../src/dataprovider.h \ + ../src/eventdata.h \ + ../src/eventsprovider.h \ + ../src/gettimeofday.h \ + ../src/globaleventsprovider.h \ + ../src/imagecreator.h \ + ../src/itemgroupview.h \ + ../src/itemiconview.h \ + ../src/itempalette.h \ + ../src/neuroscope.h \ + ../src/neuroscopedoc.h \ + ../src/neuroscopeview.h \ + ../src/neuroscopexmlreader.h \ + ../src/nevclustersprovider.h \ + ../src/neveventsprovider.h \ + ../src/nsxtracesprovider.h \ + ../src/parameterxmlcreator.h \ + ../src/parameterxmlmodifier.h \ + ../src/positionproperties.h \ + ../src/positionpropertieslayout.h \ + ../src/positionsprovider.h \ + ../src/positionview.h \ + ../src/prefdefaults.h \ + ../src/prefdefaultslayout.h \ + ../src/prefdialog.h \ + ../src/prefgeneral.h \ + ../src/prefgenerallayout.h \ + ../src/properties.h \ + ../src/propertiesdialog.h \ + ../src/propertieslayout.h \ + ../src/sessionInformation.h \ + ../src/sessionxmlwriter.h \ + ../src/tags.h \ + ../src/timer.h \ + ../src/tracesprovider.h \ + ../src/traceview.h \ + ../src/tracewidget.h \ + ../src/config-neuroscope.h\ + ../src/hdf5utilities.h \ + ../src/nwblocations.h \ + ../src/nwbreader.h \ + ../src/blackrock.h \ + ../src/cerebusclustersprovider.h \ + ../src/cerebuseventsprovider.h \ + ../src/cerebustraceprovider.h \ + ../src/nevclustersprovider.h \ + ../src/neveventsprovider.h \ + ../src/nsxtracesprovider.h \ + ../src/nwbtracesprovider.h \ + ../src/alttracesprovider.h \ + ../src/nwbeventsprovider.h \ + ../src/nwbclustersprovider.h + +INCLUDEPATH += ../../libneuosuite/src +INCLUDEPATH += ../../libneuosuite/src/shared +INCLUDEPATH += ../../libneuosuite/src/gui +INCLUDEPATH += ../../libneuosuite/src/page +INCLUDEPATH += ../../libneuosuite/src/gui/page + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +RESOURCES += \ + ../src/neuroscope-icons.qrc + +FORMS += \ + ../src/clusterpropertieslayout.ui \ + ../src/positionpropertieslayout.ui \ + ../src/prefdefaultslayout.ui \ + ../src/prefgenerallayout.ui \ + ../src/propertieslayout.ui + +DISTFILES += \ + ../src/data-file.desktop \ + ../src/eeg-file.desktop \ + ../src/filter-file.desktop \ + ../src/neuroscope.desktop \ + ../src/neuroscope-session.desktop \ + ../src/neuroscope.xml \ + ../src/neuroscope_session.xsd \ + ../src/parameter.xsd \ + ../src/hi16-app-neuroscope.png \ + ../src/hi16-nphys-nrs.png \ + ../src/hi22-app-neuroscope.png \ + ../src/hi22-nphys-nrs.png \ + ../src/hi32-app-neuroscope.png \ + ../src/hi32-nphys-nrs.png \ + ../src/hi48-app-neuroscope.png \ + ../src/hi48-nphys-nrs.png \ + ../src/hi64-app-neuroscope.png \ + ../src/hi64-nphys-nrs.png \ + ../src/neuroscope.ico \ + ../src/neuroscope.icns \ + ../src/nphys-nrs.icns \ + ../src/neuroscope.lsm \ + ../src/test \ + ../src/CMakeLists.txt \ + ../src/neuroscope.rc + +# Link the libneurosuite library +win32:CONFIG(release, debug|release) { + INCLUDEPATH += $$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug/debug + DEPENDPATH += $$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug/debug + LIBS += -L$$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Release/release/ -llibneurosuite +} +else:win32:CONFIG(debug, debug|release) { + INCLUDEPATH += $$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Release/release + DEPENDPATH += $$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Release/release + LIBS += -L$$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug/debug/ -llibneurosuite +} +else: macx:CONFIG(debug, debug|release) { + LIBNS_PATH = $$PWD/../../libneurosuite/build-libneurosuite-Unnamed_cfaaec-Debug/Debug + LIBS += -L$${LIBNS_PATH}/ -llibneurosuite.1.0.0 + INCLUDEPATH += $${LIBNS_PATH} + DEPENDPATH += $${LIBNS_PATH} +} +else: macx:CONFIG(release, debug|release) { + LIBNS_PATH = $$PWD/../../libneurosuite/build-libneurosuite-Unnamed_cfaaec-Release/Release + LIBS += -L$${LIBNS_PATH}/ -llibneurosuite.1.0.0 + INCLUDEPATH += $${LIBNS_PATH} + DEPENDPATH += $${LIBNS_PATH} +} +else:unix: LIBS += -L$$PWD/../../../Neurosuite/libneurosuite/build-libneurosuite-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug/ -llibneurosuite + + + +# Link the Cerebus library +win32:CONFIG(release, debug|release){ + LIBS += -L$$PWD/../../Cerebus/libcbsdk/build/src/release/ -lcbsdk + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src/cbhwlib + DEPENDPATH += $$PWD/../../Cerebus/libcbsdk/build/src/Release +} +else:win32:CONFIG(debug, debug|release){ + LIBS += -L$$PWD/../../Cerebus/libcbsdk/build/src/debug/ -lcbsdk + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src/cbhwlib + DEPENDPATH += $$PWD/../../Cerebus/libcbsdk/build/src/debug +} + +unix:CONFIG(release, debug|release){ + LIBS += -L$$PWD/../../Cerebus/libcbsdk/build/src/release/ -lcbsdk + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src/cbhwlib + DEPENDPATH += $$PWD/../../Cerebus/libcbsdk/build/src/Release +} +else:unix:CONFIG(debug, debug|release){ + LIBS += -L$$PWD/../../Cerebus/libcbsdk/build/src/debug/ -lcbsdk + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src + INCLUDEPATH += $$PWD/../../Cerebus/libcbsdk/src/cbhwlib + DEPENDPATH += $$PWD/../../Cerebus/libcbsdk/build/src/debug +} + + +# Link the HDF5 library +win32:CONFIG(debug, debug|release){ + HDF5_PATH = $$PWD/../../HDF5/Debug/ + + LIBS += -L$${HDF5_PATH}/ -llibszip_D -llibzlib_D -llibhdf5_D -llibhdf5_hl_cpp_D -llibhdf5_cpp_D + + INCLUDEPATH += $$PWD/../HDF5/include/ + DEPENDPATH += $$PWD/../HDF5/include/ +} +else:win32:CONFIG(release, debug|release){ + HDF5_PATH = $$PWD/../../HDF5/release/ + + LIBS += -L$${HDF5_PATH}/ -llibszip -llibzlib -llibhdf5 -llibhdf5_hl_cpp -llibhdf5_cpp + + INCLUDEPATH += $$PWD/../HDF5/include/ + DEPENDPATH += $$PWD/../HDF5/include/ +} + +unix:CONFIG(release, debug|release) { + message(Release unix hdf5) + + HDF5_PATH = $$PWD/../../HDF5/MACFiles/Release/ + + PRE_TARGETDEPS += $${HDF5_PATH}/libszip.a + PRE_TARGETDEPS += $${HDF5_PATH}/libz.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5_hl_cpp.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5_cpp.a + + LIBS += $${HDF5_PATH}/libszip.a + LIBS += $${HDF5_PATH}/libz.a + LIBS += $${HDF5_PATH}/libhdf5.a + LIBS += $${HDF5_PATH}/libhdf5_hl_cpp.a + LIBS += $${HDF5_PATH}/libhdf5_cpp.a + + INCLUDEPATH += $$PWD/../../HDF5/MACFiles/include/ + DEPENDPATH += $${HDF5_PATH} +} +else:unix:CONFIG(debug, debug|release) { + message(debug unix hdf5) + + HDF5_PATH = $$PWD/../../HDF5/MACFiles/Debug/ + + PRE_TARGETDEPS += $${HDF5_PATH}/libszip_debug.a + PRE_TARGETDEPS += $${HDF5_PATH}/libz_debug.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5_debug.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5_hl_cpp_debug.a + PRE_TARGETDEPS += $${HDF5_PATH}/libhdf5_cpp_debug.a + + LIBS += $${HDF5_PATH}/libszip_debug.a + LIBS += $${HDF5_PATH}/libz_debug.a + LIBS += $${HDF5_PATH}/libhdf5_debug.a + LIBS += $${HDF5_PATH}/libhdf5_hl_cpp_debug.a + LIBS += $${HDF5_PATH}/libhdf5_cpp_debug.a + + INCLUDEPATH += $${HDF5_PATH}/include + DEPENDPATH += $${HDF5_PATH}/Debug +} + +# Make an attempt to bundle the app on the Mac +osx:CONFIG(release, debug|release) { + CONFIG += app_bundle + mySetOfExtraFiles.files = $$DISTFILES + mySetOfExtraFiles.path = Contents/Resources + QMAKE_BUNDLE_DATA += mySetOfExtraFiles +} + + + diff --git a/neuroscope/neuroscope.pro.user b/neuroscope/neuroscope.pro.user new file mode 100644 index 0000000..c49d093 --- /dev/null +++ b/neuroscope/neuroscope.pro.user @@ -0,0 +1,328 @@ + + + + + + EnvironmentId + {1c1b9f21-31c5-4fb2-93bd-19ba067bdb0d} + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + true + false + true + + Cpp + + CppGlobal + + + + QmlJS + + QmlJSGlobal + + + 2 + UTF-8 + false + 4 + false + 80 + true + true + 1 + true + false + 0 + true + true + 0 + 8 + true + 1 + true + true + true + false + + + + ProjectExplorer.Project.PluginSettings + + + -fno-delayed-template-parsing + + true + + + + ProjectExplorer.Project.Target.0 + + Desktop Qt 5.12.2 MSVC2017 64bit + Desktop Qt 5.12.2 MSVC2017 64bit + qt.qt5.5122.win64_msvc2017_64_kit + 0 + 0 + 0 + + D:/Gigs/Neurosuite/neuroscope/build-neuroscope-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + false + false + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Debug + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + true + + + D:/Gigs/Neurosuite/neuroscope/build-neuroscope-Desktop_Qt_5_12_2_MSVC2017_64bit-Release + + + true + qmake + + QtProjectManager.QMakeBuildStep + false + + false + false + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Release + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + + D:/Gigs/Neurosuite/neuroscope/build-neuroscope-Desktop_Qt_5_12_2_MSVC2017_64bit-Profile + + + true + qmake + + QtProjectManager.QMakeBuildStep + true + + false + true + true + + + true + Make + + Qt4ProjectManager.MakeStep + + false + + + false + + 2 + Build + + ProjectExplorer.BuildSteps.Build + + + + true + Make + + Qt4ProjectManager.MakeStep + + true + clean + + false + + 1 + Clean + + ProjectExplorer.BuildSteps.Clean + + 2 + false + + Profile + Profile + Qt4ProjectManager.Qt4BuildConfiguration + 0 + true + + 3 + + + 0 + Deploy + + ProjectExplorer.BuildSteps.Deploy + + 1 + Deploy Configuration + + ProjectExplorer.DefaultDeployConfiguration + + 1 + + + false + false + 1000 + + true + + false + false + false + false + true + 0.01 + 10 + true + 1 + 25 + + 1 + true + false + true + valgrind + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + + 2 + + neuroscope + + Qt4ProjectManager.Qt4RunConfiguration:D:/Gigs/Neurosuite/neuroscope/neuroscope/neuroscope.pro + neuroscope.pro + + 3768 + false + true + true + false + false + true + + D:/Gigs/Neurosuite/neuroscope/build-neuroscope-Desktop_Qt_5_12_2_MSVC2017_64bit-Debug + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 20 + + + Version + 20 + + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 904521a..601e0da 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,106 +1,184 @@ -project(neuroscope-src) -set(neuroscope_SRCS - main.cpp - neuroscope.cpp - neuroscopeview.cpp - baseframe.cpp - channelgroupview.cpp - channeliconview.cpp - channelpalette.cpp - channelmimedata.cpp - clustercolors.cpp - clusterproperties.cpp - clustersprovider.cpp - configuration.cpp - dataprovider.cpp - eventsprovider.cpp - itemgroupview.cpp - itemiconview.cpp - itempalette.cpp - neuroscopedoc.cpp - neuroscopexmlreader.cpp - positionproperties.cpp - positionsprovider.cpp - positionview.cpp - prefdefaults.cpp - prefdialog.cpp - prefgeneral.cpp - properties.cpp - propertiesdialog.cpp - tags.cpp - tracesprovider.cpp - traceview.cpp - tracewidget.cpp - sessionxmlwriter.cpp - parameterxmlcreator.cpp - parameterxmlmodifier.cpp - imagecreator.cpp - globaleventsprovider.cpp - eventdata.cpp - clusterpropertieslayout.cpp - positionpropertieslayout.cpp - prefdefaultslayout.cpp - prefgenerallayout.cpp - propertieslayout.cpp - ) - -qt4_add_resources(neuroscope_SRCS neuroscope-icons.qrc) - -set(neuroscope_UI propertieslayout.ui prefdefaultslayout.ui positionpropertieslayout.ui prefgenerallayout.ui clusterpropertieslayout.ui) - -qt4_wrap_ui(neuroscope_SRCS ${neuroscope_UI}) - -if(USE_MACOSX_BUNDLE) - add_executable(neuroscope MACOSX_BUNDLE ${neuroscope_SRCS}) +################################################################################ +# Build config +################################################################################ +# Normal C++ source files +set(SOURCE_FILES + main.cpp + neuroscope.cpp + neuroscopeview.cpp + baseframe.cpp + channelgroupview.cpp + channeliconview.cpp + channelpalette.cpp + channelmimedata.cpp + clustercolors.cpp + clusterproperties.cpp + clustersprovider.cpp + nevclustersprovider.cpp + configuration.cpp + dataprovider.cpp + eventsprovider.cpp + neveventsprovider.cpp + itemgroupview.cpp + itemiconview.cpp + itempalette.cpp + neuroscopedoc.cpp + neuroscopexmlreader.cpp + positionproperties.cpp + positionsprovider.cpp + positionview.cpp + prefdefaults.cpp + prefdialog.cpp + prefgeneral.cpp + properties.cpp + propertiesdialog.cpp + tags.cpp + tracesprovider.cpp + nsxtracesprovider.cpp + traceview.cpp + tracewidget.cpp + sessionxmlwriter.cpp + parameterxmlcreator.cpp + parameterxmlmodifier.cpp + imagecreator.cpp + globaleventsprovider.cpp + eventdata.cpp + clusterpropertieslayout.cpp + positionpropertieslayout.cpp + prefdefaultslayout.cpp + prefgenerallayout.cpp + propertieslayout.cpp +) + +# windows resources +if(WIN32) + list(APPEND SOURCE_FILES + neuroscope.rc) +endif() + +# Qt resources +if(WITH_QT4) + qt4_add_resources(SOURCE_FILES neuroscope-icons.qrc) else() - add_executable(neuroscope WIN32 ${neuroscope_SRCS}) + qt5_add_resources(SOURCE_FILES neuroscope-icons.qrc) endif() -if(Qt5Core_FOUND) - # Since Qt5WebKitWidgets is optional, we use the old variable here that will not throw an error if not set. - target_link_libraries( neuroscope ${LIBKLUSTERSSHARED_LIBRARY} Qt5::Widgets Qt5::Xml Qt5::PrintSupport Qt5::WinMain ${Qt5WebKitWidgets_LIBRARIES}) -else(Qt5Core_FOUND) - target_link_libraries( neuroscope ${LIBKLUSTERSSHARED_LIBRARY} ${QT_LIBRARIES} ${QT_QTXML_LIBRARY}) +# Ui files +set(UI_FILES + propertieslayout.ui + prefdefaultslayout.ui + positionpropertieslayout.ui + prefgenerallayout.ui + clusterpropertieslayout.ui +) + +if(WITH_QT4) + qt4_wrap_ui(SOURCE_FILES ${UI_FILES}) +else() + qt5_wrap_ui(SOURCE_FILES ${UI_FILES}) endif() -if(WIN32) - install(TARGETS neuroscope DESTINATION bin) - - # Use fix_bundle to install qt libraries - set(APPS "\${CMAKE_INSTALL_PREFIX}/bin/neuroscope.exe") # paths to executables - set(DIRS "${QTDIR}/lib;${QTDIR}/bin") # directories to search for prerequisites - - INSTALL(CODE "include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\")") -elseif(USE_MACOSX_BUNDLE) - install(TARGETS neuroscope DESTINATION ".") - - set(MACOSX_BUNDLE_RESOURCES "${CMAKE_CURRENT_BINARY_DIR}/neuroscope.app/Contents/Resources") - set(MACOSX_BUNDLE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/neuroscope.icns") - execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${MACOSX_BUNDLE_RESOURCES}) - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_if_different ${MACOSX_BUNDLE_ICON} ${MACOSX_BUNDLE_RESOURCES}) - - # Use DeployQt4 to install qt libraries - include(DeployQt4) - install_qt4_executable("neuroscope.app") +# Optional C++ files +if(WITH_CEREBUS) + # Add network specific source files + list(APPEND SOURCE_FILES + cerebustraceprovider.cpp + cerebusclustersprovider.cpp + cerebuseventsprovider.cpp) +endif() + +# Combine it all +if(APPBUNDLE) + add_executable(neuroscope MACOSX_BUNDLE ${SOURCE_FILES}) +else() + add_executable(neuroscope WIN32 ${SOURCE_FILES}) +endif() + +################################################################################ +# Linker config +################################################################################ +target_link_libraries(neuroscope neurosuite) + +if(WITH_QT4) + target_link_libraries(neuroscope Qt4::QtGui Qt4::QtXml) else() - install(TARGETS neuroscope DESTINATION bin) - - install(FILES hi16-app-neuroscope.png DESTINATION share/icons/hicolor/16x16/apps/ RENAME neuroscope.png) - install(FILES hi22-app-neuroscope.png DESTINATION share/icons/hicolor/22x22/apps/ RENAME neuroscope.png) - install(FILES hi32-app-neuroscope.png DESTINATION share/icons/hicolor/32x32/apps/ RENAME neuroscope.png) - install(FILES hi48-app-neuroscope.png DESTINATION share/icons/hicolor/48x48/apps/ RENAME neuroscope.png) - install(FILES hi64-app-neuroscope.png DESTINATION share/icons/hicolor/64x64/apps/ RENAME neuroscope.png) - - install(FILES hi16-nphys-nrs.png DESTINATION share/icons/hicolor/16x16/mimetypes/ RENAME application-nphys-nrs.png) - install(FILES hi22-nphys-nrs.png DESTINATION share/icons/hicolor/22x22/mimetypes/ RENAME application-nphys-nrs.png) - install(FILES hi32-nphys-nrs.png DESTINATION share/icons/hicolor/32x32/mimetypes/ RENAME application-nphys-nrs.png) - install(FILES hi48-nphys-nrs.png DESTINATION share/icons/hicolor/48x48/mimetypes/ RENAME application-nphys-nrs.png) - install(FILES hi64-nphys-nrs.png DESTINATION share/icons/hicolor/64x64/mimetypes/ RENAME application-nphys-nrs.png) - - install(FILES neuroscope.desktop DESTINATION share/applications/) - install(FILES neuroscope.xml DESTINATION share/mime/packages/) + target_link_libraries(neuroscope Qt5::Widgets Qt5::Xml Qt5::PrintSupport) +endif() + +if(WITH_CEREBUS) + target_link_libraries(neuroscope cbsdk) endif() +################################################################################ +# Install config +################################################################################ +if(WIN32) + install(TARGETS neuroscope DESTINATION bin) + + include(DeployNeurosuite) + install_neurosuite_dependencies( + "bin/neuroscope.exe" + ) +elseif(APPBUNDLE) + install(TARGETS neuroscope DESTINATION ".") + # Set and create Resources directory and install icon + set(MACOSX_BUNDLE_RESOURCES + "${CMAKE_CURRENT_BINARY_DIR}/neuroscope.app/Contents/Resources" + ) + set(MACOSX_BUNDLE_ICON + "${CMAKE_CURRENT_SOURCE_DIR}/neuroscope.icns" + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E make_directory ${MACOSX_BUNDLE_RESOURCES} + ) + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${MACOSX_BUNDLE_ICON} ${MACOSX_BUNDLE_RESOURCES} + ) + include(DeployNeurosuite) + install_neurosuite_dependencies( + "neuroscope.app" + ) +else() + install(TARGETS neuroscope DESTINATION bin) + + if(NOT APPLE) + install(FILES hi16-app-neuroscope.png + DESTINATION share/icons/hicolor/16x16/apps/ + RENAME neuroscope.png) + install(FILES hi22-app-neuroscope.png + DESTINATION share/icons/hicolor/22x22/apps/ + RENAME neuroscope.png) + install(FILES hi32-app-neuroscope.png + DESTINATION share/icons/hicolor/32x32/apps/ + RENAME neuroscope.png) + install(FILES hi48-app-neuroscope.png + DESTINATION share/icons/hicolor/48x48/apps/ + RENAME neuroscope.png) + install(FILES hi64-app-neuroscope.png + DESTINATION share/icons/hicolor/64x64/apps/ + RENAME neuroscope.png) + + install(FILES hi16-nphys-nrs.png + DESTINATION share/icons/hicolor/16x16/mimetypes/ + RENAME application-nphys-nrs.png) + install(FILES hi22-nphys-nrs.png + DESTINATION share/icons/hicolor/22x22/mimetypes/ + RENAME application-nphys-nrs.png) + install(FILES hi32-nphys-nrs.png + DESTINATION share/icons/hicolor/32x32/mimetypes/ + RENAME application-nphys-nrs.png) + install(FILES hi48-nphys-nrs.png + DESTINATION share/icons/hicolor/48x48/mimetypes/ + RENAME application-nphys-nrs.png) + install(FILES hi64-nphys-nrs.png + DESTINATION share/icons/hicolor/64x64/mimetypes/ + RENAME application-nphys-nrs.png) + + install(FILES neuroscope.desktop + DESTINATION share/applications/) + install(FILES neuroscope.xml + DESTINATION share/mime/packages/) + endif() +endif() diff --git a/src/ReadHDF5.cpp b/src/ReadHDF5.cpp new file mode 100644 index 0000000..4b1e174 --- /dev/null +++ b/src/ReadHDF5.cpp @@ -0,0 +1,58 @@ + + + +#ifdef OLD_HEADER_FILENAME +#include +#else +#include +#endif +using std::cout; +using std::endl; +#include +#include "H5Cpp.h" +#include "hdf5.h" +#include "H5File.h" +using namespace H5; +#include "neuroscopexmlreader.h" +#include + +#include + + + + + +#ifdef TRASH +def get3RawSpikeTimes(nwb_spike_times, nwb_spike_times_index, nwb_units_electrode_group, myfile): + spikeTimes = myfile[nwb_spike_times] + spikeTimeIndex = myfile[nwb_spike_times_index] + ElectrodeGroup = myfile[nwb_units_electrode_group] + return(spikeTimes, spikeTimeIndex, ElectrodeGroup) + +def mapSpikeUnits(spikeTimes, spikeTimeIndex, ElectrodeGroup, myfile): + units = [] + for idx, ndxUpper in enumerate(spikeTimeIndex): + ndxLower = 0 if idx == 0 else spikeTimeIndex[idx-1] + units.append((myfile[ElectrodeGroup[idx]].name, spikeTimes[ndxLower:ndxUpper])) + return units + +def getSpikeUnits(nwb_spike_times, nwb_spike_times_index, nwb_units_electrode_group, myfile): + (spikeTimes, spikeTimeIndex, ElectrodeGroup) = get3RawSpikeTimes(nwb_spike_times, nwb_spike_times_index, nwb_units_electrode_group, myfile) + units = mapSpikeUnits(spikeTimes, spikeTimeIndex, ElectrodeGroup, myfile) + return units + +def stripShankName(strIn): + strSplit = strIn.split('/') + return (strSplit[-1] if len(strSplit)>0 else strIn) + + +nwb_spike_times = 'units/spike_times' +nwb_spike_times_index = 'units/spike_times_index' +nwb_units_electrode_group = 'units/electrode_group' + +units = getSpikeUnits(nwb_spike_times, nwb_spike_times_index, nwb_units_electrode_group, myfile) +for oneUnit in units: + print(stripShankName(oneUnit[0]), oneUnit[1][:8]) +#endif + + diff --git a/src/alttracesprovider.h b/src/alttracesprovider.h new file mode 100644 index 0000000..30d3491 --- /dev/null +++ b/src/alttracesprovider.h @@ -0,0 +1,119 @@ +/*************************************************************************** + AltTracesProvider.h - description + ------------------- + + Processes alternative to the standard Neuroscope TracesProvider. + Will be the base for a derived Neurodata Without Borders data .nwb provider. + Adapted by Robert H. Moore (RHM) for Dr. Ben Dichter. + + Adapted from nsxtracesprovider.h: + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef ALTTRACESPROVIDER_H +#define ALTTRACESPROVIDER_H + + +//include files for the application +#include "tracesprovider.h" +#include "array.h" +#include "types.h" + + +// include files for QT +#include +#include + +/** Class providing generic support for an Alterate Traces Provider. + * @author Florian Franzen - adapted by Robert H. Moore + */ + +class AltTracesProvider : public TracesProvider { + Q_OBJECT +public: + + /**Constructor. + * @param fileUrl the url of the file containing the data provided by this class. + */ + // AltTracesProvider(const QString &fileUrl) + AltTracesProvider(const QString &fileUrl, int nbChannels, int resolution, int voltageRange, int amplification, double samplingRate, int offset) + :TracesProvider(fileUrl, nbChannels, resolution, voltageRange, amplification, samplingRate, offset){} + virtual ~AltTracesProvider(){} + + /**Sets the number of channels corresponding to the file identified by fileUrl. + * @param nb the number of channels. + */ + virtual void setNbChannels(int nb){ + qDebug() << qsShortName() << " file used. Ignoring setNbChannels(" << nb << ")"; + } + + /**Sets the resolution used to record the data contained in the file identified by fileUrl. + * @param res resolution. + */ + virtual void setResolution(int res){ + qDebug() << qsShortName() << " file used. Ignoring setResolution(" << res << ")"; + } + + /**Sets the sampling rate used to record the data contained in the file identified by fileUrl. + * @param rate the sampling rate. + */ + virtual void setSamplingRate(double rate){ + qDebug() << qsShortName() << " file used. Ignoring setSamplingRate(" << rate << ")"; + } + + /**Sets the voltage range used to record the data contained in the file identified by fileUrl. + * @param range the voltage range. + */ + virtual void setVoltageRange(int range){ + qDebug() << qsShortName() << " file used. Ignoring setVoltageRange(" << range << ")"; + } + + /**Sets the amplification used to record the data contained in the file identified by fileUrl. + * @param value the amplification. + */ + virtual void setAmplification(int value){ + qDebug() << qsShortName() << " file used. Ignoring setAmplification(" << value << ")"; + } + + /** Initializes the object by reading the -- files header. + */ + virtual bool init(){return false;} + + /**Computes the number of samples between @p start and @p end. + * @param start begining of the time frame from which the data have been retrieved, given in milisecond. + * @param end end of the time frame from which to retrieve the data, given in milisecond. + * @return number of samples in the given time frame. + * @param startTimeInRecordingUnits begining of the time frame from which the data have been retrieved, given in recording units. + */ + virtual long getNbSamples(long /*start*/, long /*end*/, long /*startInRecordingUnits*/) {return 0;} + + /** Return the labels of each channel as read from --- file. */ + virtual QStringList getLabels() {QStringList labels; return labels;} + +Q_SIGNALS: + /**Signals that the data have been retrieved. + * @param data array of data in uV (number of channels X number of samples). + * @param initiator instance requesting the data. + */ + virtual void dataReady(Array& data,QObject* initiator); + +protected: + virtual QString qsShortName() {return "ALT";} + bool mInitialized; + +private: + +}; + + + +#endif // ALTTRACESPROVIDER_H diff --git a/src/blackrock.h b/src/blackrock.h new file mode 100644 index 0000000..e53252f --- /dev/null +++ b/src/blackrock.h @@ -0,0 +1,234 @@ +/*************************************************************************** + blackrock.h - description + ------------------- + purpose : Structs used to parse Blackrock NSX and NEV binary files + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _BLACKROCK_H_ +#define _BLACKROCK_H_ + +// include c/c++ headers +#include + +// incluse qt headers +#include + +/** Little helper function to read structs from QFiles in binary mode. */ +template +inline bool readStruct(QFile& file, T& s) { + qint64 bytesToRead = sizeof(T); + qint64 bytesRead = file.read(reinterpret_cast(&s), bytesToRead); + return (bytesToRead == bytesRead); +} + +#pragma pack(push, 1) + +// Shared data structures +typedef struct { + int16_t year; + int16_t month; + int16_t dayofweek; + int16_t day; + int16_t hour; + int16_t minute; + int16_t second; + int16_t milliseconds; +} WindowsSystemTime; + +/* + * NEV file data structure + */ +typedef struct { + char file_type[8]; // “NEURALEV” + uint16_t file_spec; // e.g. 0x0202 = Spec 2.2 + uint16_t flags; // Bit0: waveform pure 16bit + uint32_t header_size; + uint32_t data_package_size; + uint32_t global_time_resolution; // counts per minute + uint32_t waveform_time_resolution; // counts per minute + WindowsSystemTime time_origin; + char application[32]; // name of program that created this file. + char comment[256]; + uint32_t extension_count; +} NEVBasicHeader; + +// NEV Extension info section +#define NEVArrayNameID "ARRAYNME" +#define NEVExtraCommentID "ECOMMENT" +#define NEVContinuedCommentID "CCOMMENT" +#define NEVMapFileID "MAPFILE\0" +#define NEVNeuralWaveformID "NEUEVWAV" +#define NEVNeuralLabelID "NEUEVLBL" +#define NEVNeuralFilterID "NEUEVFLT" +#define NEVDigitalLabel "DIGLABEL" +#define NEVVideoSyncID "VIDEOSYN" +#define NEVTrackableObjectID "TRACKOBJ" + +typedef struct { + char id[8]; // package id (see above) + char data[24]; +} NEVExtensionHeader; + +typedef struct { + uint16_t id; // electrode id + uint8_t bank; + uint8_t pin; + uint16_t factor; // digitization scaling factor (nV per LSB step). + uint16_t threshold; // energy threshold, 0 if none used. + int16_t high_threshold; // amplitude high threshold used (in μV) 0 to 32767. + int16_t low_threshold; // amplitude low threshold used (in μV) 0 to -32767. + uint8_t units; // number of sorted units in channel, set to 0 for no unit classification + uint8_t size; // bytes per waveform sample, 0 and 1 both indicate 1 byte + uint16_t width; // samples per waveform (48 by default) + uint8_t reserved[8]; +} NEVNeuralWaveformExtensionData; + +typedef struct { + uint16_t id; // electrode id + char label[16]; + uint8_t reserved[6]; +} NEVNeuralLabelExtensionData; + +typedef struct { + uint16_t id; // electrode id + uint32_t highpass_corner; // high cutoff frequency in MHz + uint32_t highpass_order; // order of high cutoff: 0 = NONE + uint16_t highpass_type; // type of high cutoff: 0 = NONE, 1 = Butterworth + uint32_t lowpass_corner; // low cutoff frequency in MHz + uint32_t lowpass_order; // order of low cutoff: 0 = NONE + uint16_t lowpass_type; // type of low cutoff: 0 = NONE, 1 = Butterworth + uint8_t reserved[2]; +} NEVNeuralFilterExtensionData; + +typedef struct { + char label[16]; + uint8_t mode; // 0 = serial, 1 = parallel + uint8_t reserved[7]; +} NEVDigitalLabelExtensionData; + +typedef struct { + uint16_t id; // video source id + char name[16]; + float rate; // nominal fps (> 0) + uint8_t reserved[2]; +} NEVVideoSyncExtensionData; + +typedef struct { + uint16_t type; // 0 = undef, 1 = 2D body w/ markers, 2 = 2D body w/ blobs, 3 = 3D body w/ markers, 4 = 2d boundaries + uint16_t id; // trackable id + uint16_t count; // point count + char name[16]; + uint8_t reserved[2]; +} NEVTrackableObjectExtensionData; + +// NEV Data section +#define NEVDigitalSerialDataID 0x0 +// NEVSpikeDataID 0x1 - 0x800 +#define NEVConfigurationDataID 0xFFFB +#define NEVButtonDataID 0xFFFC +#define NEVTrackingDataID 0xFFFD +#define NEVVideoSyncDataID 0xFFFE +#define NEVCommentDataID 0xFFFF +#define NEVContinuationDataID 0xFFFFFFFF + +typedef struct { + uint32_t timestamp; + uint16_t id; // data header id (see above) +} NEVDataHeader; + +typedef struct { + uint8_t reason; // bit 0 = digital change, bit 7 = serial + uint8_t reserved; + uint16_t input; +} NEVDigitalSerialData; + +typedef struct { + uint8_t unit_class; // 0 = unclassified, 1-16 unit, 255 = noise + uint8_t reserved; + // Followed by uint8_t waveform[size - 8]; +} NEVSpikeDataHeader; + +typedef struct { + uint16_t type; // 0 = normal, 1 = critical; + // Followed by char change[size - 8]; +} NEVConfigurationDataHeader; + +typedef struct { + uint16_t trigger; // 0 = undefined, 1 = press, 2 = reset +} NEVButtonData; + +typedef struct { + uint16_t file_number; + uint32_t frame_number; + uint32_t elapsed_time; + uint32_t id; // video source id +} NEVVideoSyncData; + +typedef struct { + uint16_t parent_id; + uint16_t node_id; + uint16_t node_count; + uint16_t point_count; + // Followed by uint16_t points[size - 14] +} NEVTrackingDataHeader; + +typedef struct { + uint8_t char_set; // 0 = Ansi, 1 = UTF-16 + uint8_t reserved; + uint32_t color; // RGBA + // Followed by char comment[size - 12]; +} NEVCommentDataHeader; + +/* + * NSX file data structure + */ +typedef struct { + char file_type[8]; // “NEURALCD” or “NEURALSG”. + uint16_t file_spec; // e.g. 0x0202 = Spec 2.2 + uint32_t header_size; + char label[16]; + char comment[256]; + uint32_t sampling_period; // sampling period e.g. 1 for 30ks, 3 for 10ks + uint32_t time_resolution; // (counts per second) of the global clock used to index the time samples of the individual data packet entries. + WindowsSystemTime time_origin; + uint32_t channel_count; +} NSXBasicHeader; + +typedef struct { + char type[2]; // “CC” for “Continuous Channels” + uint16_t id; + char label[16]; + uint8_t bank; + uint8_t pin; + int16_t min_digital_value; + int16_t max_digital_value; + int16_t min_analog_value; + int16_t max_analog_value; + char unit[16]; // units of analog range values (“mV”, “μV”) + uint32_t highpass_corner; // high cutoff frequency in MHz + uint32_t highpass_order; // order of high cutoff: 0 = NONE + uint16_t highpass_type; // type of high cutoff: 0 = NONE, 1 = Butterworth + uint32_t lowpass_corner; // low cutoff frequency in MHz + uint32_t lowpass_order; // order of low cutoff: 0 = NONE + uint16_t lowpass_type; // type of low cutoff: 0 = NONE, 1 = Butterworth +} NSXExtensionHeader; + +typedef struct { + uint8_t header; + uint32_t timestamp; + uint32_t length; +} NSXDataHeader; + +#pragma pack(pop) // return to previous pack setting + +#endif diff --git a/src/cerebusclustersprovider.cpp b/src/cerebusclustersprovider.cpp new file mode 100644 index 0000000..712b780 --- /dev/null +++ b/src/cerebusclustersprovider.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + cerebusclustersprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "cerebusclustersprovider.h" + +#include + +CerebusClustersProvider::CerebusClustersProvider(CerebusTracesProvider* source, unsigned int channel, int samplingRate) : + ClustersProvider(QString("cerebus.%1.clu").arg(channel + 1), samplingRate, cbSdk_TICKS_PER_SECOND, 0), + mDataProvider(source), + mChannel(channel) { + + // Name referes to the group, which can not be zero, because zero is trash group. + this->name = QString::number(mChannel + 1); + // 0 = unclassified, 1 - cbMAXUNITS = actual units, 254 = artifact (unit + noise), 255 = background (noise) + this->clusterIds << 0 << 1 << 2 << 3 << 4 << 5 << 254 << 255; +} + +CerebusClustersProvider::~CerebusClustersProvider() { + // Nothing to do here +} + +int CerebusClustersProvider::loadData() { + if (mDataProvider->isInitialized()) + return OPEN_ERROR; + else + return OK; +} + +void CerebusClustersProvider::requestData(long start, long end, QObject* initiator, long /*startTimeInRecordingUnits*/) { + Array* data = mDataProvider->getClusterData(mChannel, start, end); + emit dataReady(*data, initiator, this->name); + delete data; +} + +void CerebusClustersProvider::requestNextClusterData(long startTime, long timeFrame, const QList &selectedIds, QObject* initiator, long startTimeInRecordingUnits) { + qCritical() << "requestNextClusterData(...) not supported yet."; +} + +void CerebusClustersProvider::requestPreviousClusterData(long startTime, long timeFrame, QList selectedIds, QObject* initiator, long startTimeInRecordingUnits) { + qCritical() << "requestPreviousClusterData(...) not supported yet."; +} diff --git a/src/cerebusclustersprovider.h b/src/cerebusclustersprovider.h new file mode 100644 index 0000000..f51d8d2 --- /dev/null +++ b/src/cerebusclustersprovider.h @@ -0,0 +1,79 @@ +/*************************************************************************** + cerebusclustersprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _CEREBUSCLUSTERSPROVIDER_H_ +#define _CEREBUSCLUSTERSPROVIDER_H_ + +#include "clustersprovider.h" +#include "cerebustraceprovider.h" + +class CerebusClustersProvider : public ClustersProvider { + Q_OBJECT + +public: + + CerebusClustersProvider(CerebusTracesProvider* source, unsigned int channel, int samplingRate); + ~CerebusClustersProvider(); + + /** Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + * + * Since data is supplied by CerebusTraceProvider, this is + * just a wrapper around isInitialized() + */ + virtual int loadData(); + + + /**Triggers the retrieve of the cluster information included in the time interval given by @p startTime and @p endTime. + * @param startTime begining of the time interval from which to retrieve the data in miliseconds. + * @param endTime end of the time interval from which to retrieve the data. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void requestData(long startTime, long endTime, QObject* initiator, long startTimeInRecordingUnits); + + + /**Looks up for the first of the clusters included in the list @p selectedIds existing after the time @p startTime. + * All the clusters included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first cluster found located at @p clusterPosition percentage of the time interval. + * @param startTime starting time, in miliseconds, for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of cluster ids to look up for. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits starting time, in recording units, for the look up. + */ + virtual void requestNextClusterData(long startTime, long timeFrame, const QList &selectedIds, QObject* initiator, long startTimeInRecordingUnits); + + + /**Looks up for the first of the clusters included in the list @p selectedIds existing before the time @p endTime. + * All the clusters included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first cluster found located at @p clusterPosition percentage of the time interval. + * @param startTime starting time, in miliseconds, for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of cluster ids to look up for. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits starting time, in recording units, for the look up. + */ + virtual void requestPreviousClusterData(long startTime, long timeFrame, QList selectedIds, QObject* initiator, long startTimeInRecordingUnits); + +private: + // The actual data source of the cluster data. + CerebusTracesProvider* mDataProvider; + + // Channel this provider is responsible for + unsigned int mChannel; +}; + +#endif diff --git a/src/cerebuseventsprovider.cpp b/src/cerebuseventsprovider.cpp new file mode 100644 index 0000000..d8afbce --- /dev/null +++ b/src/cerebuseventsprovider.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + cerebuseventsprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include + +#include + +#include "cerebuseventsprovider.h" + +CerebusEventsProvider::CerebusEventsProvider(CerebusTracesProvider* source, int samplingRate) : + EventsProvider("cerebus.nev", samplingRate), + mDataProvider(source) { + + EventDescription digital("Digital Event"); + this->eventIds.insert(digital, MAX_CHANS_DIGITAL_IN); + this->idsDescriptions.insert(MAX_CHANS_DIGITAL_IN, digital); + this->eventDescriptionCounter.insert(digital, 1); + + EventDescription serial("Serial Event"); + this->eventIds.insert(serial, MAX_CHANS_SERIAL); + this->idsDescriptions.insert(MAX_CHANS_SERIAL, serial); + this->eventDescriptionCounter.insert(serial, 1); + + descriptionLength = 13; +} + +CerebusEventsProvider::~CerebusEventsProvider() { + // Nothing to do here + } + +void CerebusEventsProvider::requestData(long start, long end, QObject* initiator, long /*startTimeInRecordingUnits*/) { + Array* data = mDataProvider->getEventData(start, end); + + // Split data up into two arrays + long count = data->nbOfColumns(); + + Array times(1, count); + memcpy(×[0], &(*data)[0], count * sizeof(dataType)); + + Array ids(1, count); + for(long i = 0; i < count; i++) + ids[i] = (*data)[count + i]; + + emit dataReady(times, ids, initiator, this->name); + delete data; +} + + +void CerebusEventsProvider::requestNextEventData(long startTime, long timeFrame, const QList &selectedIds, QObject* initiator) { + qCritical() << "requestNextEventData(...) not supported yet."; + } + +void CerebusEventsProvider::requestPreviousEventData(long endTime, long timeFrame, QList selectedIds, QObject* initiator) { + qCritical() << "requestPreviousEventData(...) not supported yet."; + } + +int CerebusEventsProvider::loadData() { + if (mDataProvider->isInitialized()) + return OPEN_ERROR; + else + return OK; + } diff --git a/src/cerebuseventsprovider.h b/src/cerebuseventsprovider.h new file mode 100644 index 0000000..00e6b32 --- /dev/null +++ b/src/cerebuseventsprovider.h @@ -0,0 +1,66 @@ +/*************************************************************************** + cerebuseventsprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + + #ifndef _CEREBUSEVENTSPROVIDER_H_ + #define _CEREBUSEVENTSPROVIDER_H_ + +#include "eventsprovider.h" +#include "cerebustraceprovider.h" + + class CerebusEventsProvider : public EventsProvider { + Q_OBJECT + public: + CerebusEventsProvider(CerebusTracesProvider* source, int samplingRate); + ~CerebusEventsProvider(); + + /**Triggers the retrieve of the events included in the time interval given by @p startTime and @p endTime. + * @param startTime begining of the time interval from which to retrieve the data in miliseconds. + * @param endTime end of the time interval from which to retrieve the data. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits = 0); + + /**Looks up for the first of the events included in the list @p selectedIds existing after the time @p startTime. + * All the events included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first event found located at @p eventPosition percentage of the time interval. + * @param startTime starting time for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of event ids to look up for. + * @param initiator instance requesting the data. + */ + virtual void requestNextEventData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator); + + /**Looks up for the first of the events included in the list @p selectedIds existing before the time @p endTime. + * All the events included in the time interval given by @p timeFrame are retrieved. The time interval start time is + * computed in order to have the first event found located at @p eventPosition percentage of the time interval. + * @param endTime starting time for the look up. + * @param timeFrame time interval for which to retrieve the data. + * @param selectedIds list of event ids to look up for. + * @param initiator instance requesting the data. + */ + virtual void requestPreviousEventData(long endTime,long timeFrame,QList selectedIds,QObject* initiator); + + /**Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + */ + virtual int loadData(); + + private: + // The actual data source of the event data. + CerebusTracesProvider* mDataProvider; + }; + +#endif diff --git a/src/cerebustraceprovider.cpp b/src/cerebustraceprovider.cpp new file mode 100644 index 0000000..ef7b0b4 --- /dev/null +++ b/src/cerebustraceprovider.cpp @@ -0,0 +1,804 @@ +/*************************************************************************** + cerebustracesprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "cerebustraceprovider.h" + +#include + +#include + +#include "cerebusclustersprovider.h" +#include "cerebuseventsprovider.h" + +// TODO: Fix this ugly hack by implementing our own error return value. +#define CBSDKRESULT_EMPTYSAMPLINGGROUP -50 + +const int CerebusTracesProvider::CEREBUS_RESOLUTION = 16; +const unsigned int CerebusTracesProvider::CEREBUS_INSTANCE = 0; +const unsigned int CerebusTracesProvider::BUFFER_SIZE = 10; +const unsigned int CerebusTracesProvider::SAMPLING_RATES[] = { 0, 500, 1000, 2000, 10000, 30000 }; + +CerebusTracesProvider::CerebusTracesProvider(SamplingGroup group) : + TracesProvider("", -1, CEREBUS_RESOLUTION, 0, 0, 0, 0), + mGroup(group), + mInitialized(false), + mReconfigured(false), + mScales(NULL), + mChannels(NULL), + mLiveTime(NULL), + mViewTime(NULL), + mLiveTraceData(NULL), + mLiveTracePosition(NULL), + mViewTraceData(NULL), + mViewTracePosition(NULL), + mLiveClusterTime(NULL), + mLiveClusterID(NULL), + mLiveClusterPosition(NULL), + mViewClusterTime(NULL), + mViewClusterID(NULL), + mViewClusterPosition(NULL), + mLiveEventTime(NULL), + mLiveEventID(NULL), + mLiveEventPosition(NULL), + mViewEventTime(NULL), + mViewEventID(NULL), + mViewEventPosition(NULL) { + + // The sampling rate is hardwired to the sampling group + this->samplingRate = SAMPLING_RATES[group]; + mTraceCapacity = BUFFER_SIZE * this->samplingRate; + mEventCapacity = BUFFER_SIZE * cbSdk_TICKS_PER_SECOND; + + // The buffer always has the same size + this->length = 1000 * BUFFER_SIZE; +} + +CerebusTracesProvider::~CerebusTracesProvider() { + if (mInitialized) { + // Disabled callbacks, close network thread and connection + cbSdkClose(CEREBUS_INSTANCE); + + // Free uninitalize internal structures + delete[] mScales; + delete[] mChannels; + + if(mViewTraceData != mLiveTraceData) { + // We are in paused mode + delete mViewTime; + + delete[] mViewTraceData; + delete mViewTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mViewClusterTime[i]; + delete[] mViewClusterID[i]; + delete mViewClusterPosition[i]; + } + + delete[] mViewClusterTime; + delete[] mViewClusterID; + delete[] mViewClusterPosition; + + delete[] mViewEventTime; + delete[] mViewEventID; + delete mViewEventPosition; + } + delete mLiveTime; + + delete[] mLiveTraceData; + delete mLiveTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mLiveClusterTime[i]; + delete[] mLiveClusterID[i]; + delete mLiveClusterPosition[i]; + } + + delete[] mLiveClusterTime; + delete[] mLiveClusterID; + delete[] mLiveClusterPosition; + + delete[] mLiveEventTime; + delete[] mLiveEventID; + delete[] mLiveEventPosition; + } +} + +bool CerebusTracesProvider::init() { + if (mInitialized) + return true; + + // Open connection to NSP + mLastResult = cbSdkOpen(CEREBUS_INSTANCE); + + if (mLastResult != CBSDKRESULT_SUCCESS) + return false; + + // Get number of channels in sampling group + mLastResult = cbSdkGetSampleGroupList(CEREBUS_INSTANCE, 1, mGroup, (UINT32 *) &this->nbChannels, NULL); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + if (this->nbChannels == 0) { + mLastResult = CBSDKRESULT_EMPTYSAMPLINGGROUP; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Get the list of channels in this sample group + mChannels = new UINT16[this->nbChannels]; + mLastResult = cbSdkGetSampleGroupList(CEREBUS_INSTANCE, 1, mGroup, NULL, mChannels); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Get scaling for each channel of group + mScales = new cbSCALING[this->nbChannels]; + + cbPKT_CHANINFO info; + for (unsigned int i = 0; i < this->nbChannels; i++) + { + mLastResult = cbSdkGetChannelConfig(CEREBUS_INSTANCE, mChannels[i], &info); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Copy only physcal input scaling for now + mScales[i] = info.physcalin; + mLabels << QString(info.label); + } + + // Lock mutex to initalize cross-thread data structure. + mMutex.lock(); + + // Register data callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_CONTINUOUS, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Register spike event callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_SPIKE, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Register digital event callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_DIGITAL, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Register serial event callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_SERIAL, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Register config callback + mLastResult = cbSdkRegisterCallback(CEREBUS_INSTANCE, CBSDKCALLBACK_GROUPINFO, packageCallback, this); + + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Allocate time storage and request system time + mLiveTime = new UINT32(0); + + mLastResult = cbSdkGetTime(CEREBUS_INSTANCE, mLiveTime); + if (mLastResult != CBSDKRESULT_SUCCESS) { + delete[] mChannels; + delete[] mScales; + delete mLiveTime; + cbSdkClose(CEREBUS_INSTANCE); + return false; + } + + // Allocate live sample buffer + mLiveTraceData = new INT16[this->nbChannels * mTraceCapacity]; + memset(mLiveTraceData, 0, this->nbChannels * mTraceCapacity * sizeof(INT16)); + mLiveTracePosition = new size_t(0); + + // Allocate live spike event buffer + mLiveClusterTime = new UINT32*[this->nbChannels]; + mLiveClusterID = new UINT8*[this->nbChannels]; + mLiveClusterPosition = new size_t*[this->nbChannels]; + + for(int i = 0; i < this->nbChannels; i++) { + mLiveClusterTime[i] = new UINT32[mEventCapacity]; + memset(mLiveClusterTime[i], 0, mEventCapacity * sizeof(UINT32)); + mLiveClusterID[i] = new UINT8[mEventCapacity]; + memset(mLiveClusterID[i], 0, mEventCapacity * sizeof(UINT8)); + mLiveClusterPosition[i] = new size_t(0); + } + + // Allocate live digital event buffer + mLiveEventTime = new UINT32[mEventCapacity]; + memset(mLiveEventTime, 0, mEventCapacity * sizeof(UINT32)); + mLiveEventID = new UINT16[mEventCapacity]; + memset(mLiveEventID, 0, mEventCapacity * sizeof(UINT16)); + mLiveEventPosition = new size_t(0); + + // Link view buffer to live sample buffer + mViewTime = mLiveTime; + + mViewTraceData = mLiveTraceData; + mViewTracePosition = mLiveTracePosition; + + mViewClusterTime = mLiveClusterTime; + mViewClusterID = mLiveClusterID; + mViewClusterPosition = mLiveClusterPosition; + + mViewEventTime = mLiveEventTime; + mViewEventID = mLiveEventID; + mViewEventPosition = mLiveEventPosition; + + // We are done. + mInitialized = true; + mMutex.unlock(); + return true; +} + +void CerebusTracesProvider::processData(const cbPKT_GROUP* package) { + // Ignore package if for different sampling group + if (package->type != mGroup) + return; + + mMutex.lock(); + + // Channels were reconfigured, data size has changed and it is not save to copy data anymore. + if (mReconfigured) { + mMutex.unlock(); + return; + } + + // Update system time (only updated here, because events are always returned in relation to trace window) + (*mLiveTime) = package->time; + + // Copy sampled to history + memcpy(mLiveTraceData + ((*mLiveTracePosition) * this->nbChannels), package->data, this->nbChannels * sizeof(INT16)); + + // Adjust position, wrap around if necessary. + (*mLiveTracePosition)++; + if ((*mLiveTracePosition) == mTraceCapacity) (*mLiveTracePosition) = 0; + mMutex.unlock(); +} + +void CerebusTracesProvider::processSpike(const cbPKT_SPK* package) { + // Check if spike event was triggered by member of sampling group + int channelIndex = -1; + for(int i = 0; i < this->nbChannels; i++) { + if(mChannels[i] == package->chid) { + channelIndex = i; + break; + } + } + if (channelIndex == -1) + return; + + mMutex.lock(); + + // Channels were reconfigured and we now might receive data from channels we don't know about. + // (No harm, but also no point to continue. We are going to abort soon anyway.) + if (mReconfigured) { + mMutex.unlock(); + return; + } + + // Copy event data to history + mLiveClusterTime[channelIndex][*mLiveClusterPosition[channelIndex]] = package->time; + mLiveClusterID[channelIndex][*mLiveClusterPosition[channelIndex]] = package->unit; + + // Adjust position, wrap around if necessary. + (*mLiveClusterPosition[channelIndex])++; + if ((*mLiveClusterPosition[channelIndex]) == mEventCapacity) { + (*mLiveClusterPosition[channelIndex]) = 0; + } + + mMutex.unlock(); +} + +void CerebusTracesProvider::processEvent(const cbPKT_DINP* package) { + + mMutex.lock(); + + // Channels were reconfigured and we now might receive data from channels we don't know about. + // (No harm, but also no point to continue. We are going to abort soon anyway.) + if (mReconfigured) { + mMutex.unlock(); + return; + } + + // Safe time and channel of event + mLiveEventTime[*mLiveEventPosition] = package->time; + mLiveEventID[*mLiveEventPosition] = package->chid; + + (*mLiveEventPosition)++; + if ((*mLiveEventPosition) == mEventCapacity) { + (*mLiveEventPosition) = 0; + } + + mMutex.unlock(); +} + +void CerebusTracesProvider::processConfig(const cbPKT_GROUPINFO* package) { + // Ignore package if for different sampling group or channel count did not change + if (package->group != mGroup) + return; + + mMutex.lock(); + + // Tell all threads that from now on the config has changed. + mReconfigured = true; + + mMutex.unlock(); +} + +void CerebusTracesProvider::packageCallback(UINT32 /*instance*/, const cbSdkPktType type, const void* data, void* object) { + CerebusTracesProvider* provider = reinterpret_cast(object); + + if(provider && data) { + switch (type) { + case cbSdkPkt_CONTINUOUS: + provider->processData(reinterpret_cast(data)); + break; + case cbSdkPkt_SPIKE: + provider->processSpike(reinterpret_cast(data)); + break; + case cbSdkPkt_DIGITAL: + case cbSdkPkt_SERIAL: + provider->processEvent(reinterpret_cast(data)); + break; + case cbSdkPkt_GROUPINFO: + provider->processConfig(reinterpret_cast(data)); + break; + case cbSdkPkt_PACKETLOST: + // TODO: Take care of package lost here! + qWarning() << "Cerebus SDK: Package lost detected!"; + break; + } + } +} + +long CerebusTracesProvider::getNbSamples(long start, long end, long startInRecordingUnits) { + // Check if startInRecordingUnits was supplied, else compute it. + if (startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + + // The caller should have check that we do not go over the end of the file. + // The recording starts at time equals 0 and ends at length of the file minus one. + // Therefore the sample at endInRecordingUnits is never returned. + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + + return endInRecordingUnits - startInRecordingUnits; +} + + +void CerebusTracesProvider::retrieveData(long start, long end, QObject* initiator, long startInRecordingUnits) { + Array result; + + // Abort if not initalized + if (!mInitialized) { + emit dataReady(result, initiator); + return; + } + + // Check if startInRecordingUnits was supplied, else compute it. + if (startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + + // The caller should have check that we do not go over the end of the file. + // The recording starts at time equals 0 and ends at length of the file minus one. + // Therefore the sample at endInRecordingUnits is never returned. + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + + long lengthInRecordingUnits = endInRecordingUnits - startInRecordingUnits; + + // Data structered used past this line are accessed by multiple threads + mMutex.lock(); + + // If config was changed, this is the only way we can tell Neuroscope to abort. + // Make sure to only abort if not in pause mode! + if (mReconfigured && mViewTraceData == mLiveTraceData) { + mMutex.unlock(); + qCritical() << "Recofiguration not supported. Please reopen connection."; + emit dataReady(result, initiator); + return; + } + + // Allocate result array + result.setSize(lengthInRecordingUnits, this->nbChannels); + + // Copy and convert data from buffer. + for (int channel = 0; channel < this->nbChannels; channel++) { + // Compute start in relation to current ringbuffer position + size_t offset = startInRecordingUnits + (*mViewTracePosition); + + // Determine unit data is saved in + int unit_correction = 0; + char* unit_string = mScales[channel].anaunit; + if (!strncmp(unit_string, "uV", 16)) { + unit_correction = 1; + } + else if (!strncmp(unit_string, "mV", 16)) { + unit_correction = 1000; + } + else { + qWarning() << "Unknown unit for channel " << channel << ": " << unit_string; + continue; + } + + // Get all the values needed to translate measurement unit to uV + int min_digital = mScales[channel].digmin; + int range_digital = mScales[channel].digmax - min_digital; + int min_analog = mScales[channel].anamin; + int range_analog = mScales[channel].anamax - min_analog; + + // TODO: Add gain (anagain) to calculation, as soon as blackrock documents how it is supposed to be used. + + // Get all the samples for this channel + for (int i = 0; i < lengthInRecordingUnits; i++) { + size_t absolute_position = offset + i; + // Wrap around in case we reach end of buffer + if (absolute_position >= mTraceCapacity) { + absolute_position -= mTraceCapacity; + offset -= mTraceCapacity; + } + // Scale data using channel scaling + size_t index = (absolute_position * this->nbChannels) + channel; + result(i + 1, channel + 1) = static_cast((((static_cast(mViewTraceData[index]) - min_digital) / range_digital) * range_analog + min_analog) * unit_correction); + } + } + mMutex.unlock(); + + // Return data to initiator + emit dataReady(result, initiator); +} + +void CerebusTracesProvider::computeRecordingLength(){ + // Do not do anything here, the buffer size is always the same. +} + +QStringList CerebusTracesProvider::getLabels() { + return mLabels; +} + +void CerebusTracesProvider::slotPagingStarted() { + // We are already showing live data. + if(mLiveTraceData == mViewTraceData) + return; + + mMutex.lock(); + + // Delete view buffers with pause data + delete mViewTime; + + delete[] mViewTraceData; + delete mViewTracePosition; + + for(int i = 0; i < this->nbChannels; i++) { + delete[] mViewClusterTime[i]; + delete[] mViewClusterID[i]; + delete mViewClusterPosition[i]; + } + + delete[] mViewClusterTime; + delete[] mViewClusterID; + delete[] mViewClusterPosition; + + delete[] mViewEventTime; + delete[] mViewEventID; + delete mViewEventPosition; + + // Use the same buffer for new and displayed samples + mViewTime = mLiveTime; + + mViewTraceData = mLiveTraceData; + mViewTracePosition = mLiveTracePosition; + + mViewClusterTime = mLiveClusterTime; + mViewClusterID = mLiveClusterID; + mViewClusterPosition = mLiveClusterPosition; + + mViewEventTime = mLiveEventTime; + mViewEventID = mLiveEventID; + mViewEventPosition = mLiveEventPosition; + + mMutex.unlock(); +} + +void CerebusTracesProvider::slotPagingStopped() { + // Check if we are already paused. + if(mLiveTraceData != mViewTraceData) + return; + + mMutex.lock(); + + // Write all new data to a new empty buffer. + mLiveTime = new UINT32(*mViewTime); + + mLiveTraceData = new INT16[this->nbChannels * mTraceCapacity]; + memset(mLiveTraceData, 0, this->nbChannels * mTraceCapacity * sizeof(INT16)); + mLiveTracePosition = new size_t(0); + + mLiveClusterTime = new UINT32*[this->nbChannels]; + mLiveClusterID = new UINT8*[this->nbChannels]; + mLiveClusterPosition = new size_t*[this->nbChannels]; + + for(int i = 0; i < this->nbChannels; i++) { + mLiveClusterTime[i] = new UINT32[mEventCapacity]; + memset(mLiveClusterTime[i], 0, mEventCapacity * sizeof(UINT32)); + mLiveClusterID[i] = new UINT8[mEventCapacity]; + memset(mLiveClusterID[i], 0, mEventCapacity * sizeof(UINT8)); + mLiveClusterPosition[i] = new size_t(0); + } + + mLiveEventTime = new UINT32[mEventCapacity]; + memset(mLiveEventTime, 0, mEventCapacity * sizeof(UINT32)); + mLiveEventID = new UINT16[mEventCapacity]; + memset(mLiveEventID, 0, mEventCapacity * sizeof(UINT16)); + mLiveEventPosition = new size_t(0); + + mMutex.unlock(); +} + +std::string CerebusTracesProvider::getLastErrorMessage() { + switch (mLastResult) { + case CBSDKRESULT_WARNCONVERT: + return "If file conversion is needed"; + case CBSDKRESULT_WARNCLOSED: + return "Library is already closed"; + case CBSDKRESULT_WARNOPEN: + return "Library is already opened"; + case CBSDKRESULT_SUCCESS: + return "Successful operation"; + case CBSDKRESULT_NOTIMPLEMENTED: + return "Not implemented"; + case CBSDKRESULT_UNKNOWN: + return "Unknown error"; + case CBSDKRESULT_INVALIDPARAM: + return "Invalid parameter"; + case CBSDKRESULT_CLOSED: + return "Interface is closed cannot do this operation"; + case CBSDKRESULT_OPEN: + return "Interface is open cannot do this operation"; + case CBSDKRESULT_NULLPTR: + return "Null pointer"; + case CBSDKRESULT_ERROPENCENTRAL: + return "Unable to open Central interface"; + case CBSDKRESULT_ERROPENUDP: + return "Unable to open UDP interface (might happen if default)"; + case CBSDKRESULT_ERROPENUDPPORT: + return "Unable to open UDP port"; + case CBSDKRESULT_ERRMEMORYTRIAL: + return "Unable to allocate RAM for trial cache data"; + case CBSDKRESULT_ERROPENUDPTHREAD: + return "Unable to open UDP timer thread"; + case CBSDKRESULT_ERROPENCENTRALTHREAD: + return "Unable to open Central communication thread"; + case CBSDKRESULT_INVALIDCHANNEL: + return "Invalid channel number"; + case CBSDKRESULT_INVALIDCOMMENT: + return "Comment too long or invalid"; + case CBSDKRESULT_INVALIDFILENAME: + return "Filename too long or invalid"; + case CBSDKRESULT_INVALIDCALLBACKTYPE: + return "Invalid callback type"; + case CBSDKRESULT_CALLBACKREGFAILED: + return "Callback register/unregister failed"; + case CBSDKRESULT_ERRCONFIG: + return "Trying to run an unconfigured method"; + case CBSDKRESULT_INVALIDTRACKABLE: + return "Invalid trackable id, or trackable not present"; + case CBSDKRESULT_INVALIDVIDEOSRC: + return "Invalid video source id, or video source not present"; + case CBSDKRESULT_ERROPENFILE: + return "Cannot open file"; + case CBSDKRESULT_ERRFORMATFILE: + return "Wrong file format"; + case CBSDKRESULT_OPTERRUDP: + return "Socket option error (possibly permission issue)"; + case CBSDKRESULT_MEMERRUDP: + return "Socket memory assignment error"; + case CBSDKRESULT_INVALIDINST: + return "Invalid range or instrument address"; + case CBSDKRESULT_ERRMEMORY: + return "library memory allocation error"; + case CBSDKRESULT_ERRINIT: + return "Library initialization error"; + case CBSDKRESULT_TIMEOUT: + return "Conection timeout error"; + case CBSDKRESULT_BUSY: + return "Resource is busy"; + case CBSDKRESULT_ERROFFLINE: + return "Instrument is offline"; + case CBSDKRESULT_INSTOUTDATED: + return "The instrument runs an outdated firmware version."; + case CBSDKRESULT_LIBOUTDATED: + return "Neuroscope uses an outdated version of libcbsdk."; + case CBSDKRESULT_EMPTYSAMPLINGGROUP: + return "No channels in selected sampling group."; + } + return "Unknown error code."; +} + +QList CerebusTracesProvider::getClusterProviders() { + QList list; + + if(mInitialized) { + // Return a ClustersProvider wrapper for each channel. + for(int i = 0; i < this->nbChannels; i++) { + list.append(new CerebusClustersProvider(this, i, this->samplingRate)); + } + } + + return list; +} + +Array* CerebusTracesProvider::getClusterData(unsigned int channel, long start, long end) { + return getTimeStampedData(mViewClusterTime[channel], + mViewClusterID[channel], + mViewClusterPosition[channel], + start, + end); +} + +EventsProvider* CerebusTracesProvider::getEventProvider() { + if(!mInitialized) + return NULL; + + return new CerebusEventsProvider(this, this->samplingRate); +} + +Array* CerebusTracesProvider::getEventData(long start, long end) { + return getTimeStampedData(mViewEventTime, + mViewEventID, + mViewEventPosition, + start, + end); +} + +template +Array* CerebusTracesProvider::getTimeStampedData(UINT32* timeBuffer, + T* dataBuffer, + size_t* bufferPosition, + long start, + long end) { + // The arrays assignment operator is broken, so returning a pointer is a quick fix. + Array* result = new Array ; + + // Abort if not initalized + if(!mInitialized) + return result; + + // Determine start and end index + long startInRecordingUnits = cbSdk_TICKS_PER_SECOND * start / 1000.0; + long endInRecordingUnits = cbSdk_TICKS_PER_SECOND * end / 1000.0; + + // Compute values in relation to end of window. + startInRecordingUnits -= mEventCapacity; + endInRecordingUnits -= mEventCapacity; + + Q_ASSERT(startInRecordingUnits <= 0); + Q_ASSERT(endInRecordingUnits <= 0); + + // Compute correction factor for timestamps to return + double clockToSampleRatio = cbSdk_TICKS_PER_SECOND / this->samplingRate; + + mMutex.lock(); + + // Correct start and end with current time stamp + startInRecordingUnits += (*mViewTime); + endInRecordingUnits += (*mViewTime); + + // In the beginning or on clock overflow make sure we stay positive. + if (startInRecordingUnits < 0) + startInRecordingUnits = 0; + if (endInRecordingUnits < 0) + endInRecordingUnits = 0; + + // Linear search for start and end index. + size_t endIndex = (*bufferPosition); + do { + if(endIndex == 0) endIndex = mEventCapacity; + endIndex--; + } while(endIndex != (*bufferPosition) && + timeBuffer[endIndex] > endInRecordingUnits); + endIndex++; + + size_t startIndex = endIndex; + do { + if(startIndex == 0) startIndex = mEventCapacity; + startIndex--; + } while(startIndex != (*bufferPosition) && + timeBuffer[startIndex] >= startInRecordingUnits); + startIndex++; + + // Count spike event and wrapp around if needed. + size_t eventCount = mEventCapacity + endIndex - startIndex; + if(eventCount >= mEventCapacity) + eventCount -= mEventCapacity; + + // Abort if there are no spikes + if(eventCount == 0) { + mMutex.unlock(); + return result; + } + + // Adjust result to number of events found + result->setSize(2, eventCount); + + size_t dataIndex = 0; + if(startIndex <= endIndex) { + // Adjust and copy timestamps + for(size_t i = startIndex; i < endIndex; i++) { + (*result)[dataIndex++] = (timeBuffer[i] - startInRecordingUnits) / clockToSampleRatio; + } + // Copy cluster ids + for(size_t i = startIndex; i < endIndex; i++) { + (*result)[dataIndex++] = dataBuffer[i]; + } + } else { + // Adjust and copy timestamps + for(size_t i = startIndex; i < mEventCapacity; i++) { + (*result)[dataIndex++] = (timeBuffer[i] - startInRecordingUnits) / clockToSampleRatio; + } + for(size_t i = 0; i < endIndex; i++) { + (*result)[dataIndex++] = (timeBuffer[i] - startInRecordingUnits) / clockToSampleRatio; + } + // Copy cluster ids + for(size_t i = startIndex; i < mEventCapacity; i++) { + (*result)[dataIndex++] = dataBuffer[i]; + } + for(size_t i = 0; i < endIndex; i++) { + (*result)[dataIndex++] = dataBuffer[i]; + } + } + + Q_ASSERT(dataIndex == 2 * eventCount); + + mMutex.unlock(); + + return result; +} diff --git a/src/cerebustraceprovider.h b/src/cerebustraceprovider.h new file mode 100644 index 0000000..09bbd5f --- /dev/null +++ b/src/cerebustraceprovider.h @@ -0,0 +1,258 @@ +/*************************************************************************** + cerebustracesprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef CEREBUSTRACESPROVIDER_H +#define CEREBUSTRACESPROVIDER_H + +// Include Qt Library files +#include +#include +#include + +// Include cerebus sdk +#include + +// Include project files +#include "types.h" +#include "tracesprovider.h" +#include "clustersprovider.h" +#include "eventsprovider.h" + + +/** CerebusTracesProvider uses a Blackrock Cerebus NSP as data source. + * + * Continous recorded channels are grouped into so called sampling groups based + * on their sampling rate. This class allows you to use one of these sampling + * group as data source for traces. + * + * If the number of channels in the sampling group change, the is currently no + * way to let the GUI know. + * + * @author Florian Franzen + */ + +class CerebusTracesProvider : public TracesProvider { + Q_OBJECT + +public: + // Callback that reacts to new data + static void packageCallback(UINT32 instance, const cbSdkPktType type, const void* data, void* object); + + // Different sampling groups one can describe to + enum SamplingGroup { + RATE_500 = 1, + RATE_1K = 2, + RATE_2K = 3, + RATE_10k = 4, + RATE_30k = 5 + }; + + /** + * @param group is the sampling group to subscribe to. + */ + CerebusTracesProvider(SamplingGroup group); + virtual ~CerebusTracesProvider(); + + /** Initializes the object by trying to connect to the Cerebus NSP. + * + * Fails on connection error or when there are no channels in sampling group. + */ + bool init(); + + /** Returns true if initialized */ + bool isInitialized() { + return mInitialized; + } + + /**Computes the number of samples between @p start and @p end. + * @param start begining of the time frame from which the data have been retrieved, given in milisecond. + * @param end end of the time frame from which to retrieve the data, given in milisecond. + * @return number of samples in the given time frame. + * @param startTimeInRecordingUnits begining of the time frame from which the data have been retrieved, given in recording units. + */ + virtual long getNbSamples(long start, long end, long startInRecordingUnits); + + // Called by callback to add data to buffer. + void processData(const cbPKT_GROUP* package); + + // Called by callback to add spike event to buffer. + void processSpike(const cbPKT_SPK* package); + + // Called by callback to add digital and serial event to buffer. + void processEvent(const cbPKT_DINP* package); + + // Called by callback to process configuration changes. + void processConfig(const cbPKT_GROUPINFO* package); + + // Return last error message as string + std::string getLastErrorMessage(); + + /** Dummy function, definded to work around the bad interface design. + * @param nb the number of channels. + */ + virtual void setNbChannels(int nb){ + qDebug() << "Cerebus NSP used. Ignoring setNbChannels(" << nb << ")"; + } + + /** Dummy function, definded to work around the bad interface design. + * @param res resolution. + */ + virtual void setResolution(int res){ + qDebug() << "Cerebus NSP used. Ignoring setResolution(" << res << ")"; + } + + /** Dummy function, definded to work around the bad interface design. + * @param rate the sampling rate. + */ + virtual void setSamplingRate(double rate){ + qDebug() << "Cerebus NSP used. Ignoring setSamplingRate(" << rate << ")"; + } + + /** Dummy function, definded to work around the bad interface design. + * @param range the voltage range. + */ + virtual void setVoltageRange(int range){ + qDebug() << "Cerebus NSP used. Ignoring setVoltageRange(" << range << ")"; + } + + /** Dummy function, definded to work around the bad interface design. + * @param value the amplification. + */ + virtual void setAmplification(int value){ + qDebug() << "Cerebus NSP used. Ignoring setAmplification(" << value << ")"; + } + + /** Return the labels of each channel as read from cerebus config. */ + virtual QStringList getLabels(); + + /** Called when paging is started. + * Recouples the buffer that is updated with the one that is + * viewed/returened. + * This essentially unpauses the signal being played and shows the + * live signal again. + */ + virtual void slotPagingStarted(); + + /** Called when paging is stopped. + * Decouples the buffer that is viewed/returned from the one updated. + * This essentialy pauses the signal that is being displayed. + */ + virtual void slotPagingStopped(); + + /** Create a cluster provider for each channel. + */ + QList getClusterProviders(); + + /** Get cluster data for specific channel in time between @start and @end. + */ + Array* getClusterData(unsigned int channel, long start, long end); + + /** Create a event provider for all event data. + */ + EventsProvider* getEventProvider(); + + /** Get event data for all events between @start and @end. + */ + Array* getEventData(long start, long end); + +Q_SIGNALS: + /**Signals that the data have been retrieved. + * @param data array of data in uV (number of channels X number of samples). + * @param initiator instance requesting the data. + */ + void dataReady(Array& data, QObject* initiator); + +private: + // Resolution of data packages received. + static const int CEREBUS_RESOLUTION ; + // Default instance id to use to talk to CB SDK + static const unsigned int CEREBUS_INSTANCE; + // Length of buffer in seconds (for events it is assumed there is an event for every tick in that second) + static const unsigned int BUFFER_SIZE; + // Sampling rate of each sampling group + static const unsigned int SAMPLING_RATES[6]; + + // True if connection, buffers and callback were initialized + bool mInitialized; + // True if set of channels in sampling group were changed + bool mReconfigured; + + // Sampling group to listen to + SamplingGroup mGroup; + // List of NSP channel numbers we are listing to + UINT16* mChannels; + + // List of scalings for each channel + cbSCALING* mScales; + // List of labels for each channel + QStringList mLabels; + + // Return value of last CBSDK library call + int mLastResult; + + // Data storage mutex + QMutex mMutex; + + // Latest NSP system clock value + UINT32* mLiveTime; + UINT32* mViewTime; + + // Capacity of buffers + size_t mTraceCapacity; + size_t mEventCapacity; + + // Continous data storage + INT16* mLiveTraceData; + size_t* mLiveTracePosition; + INT16* mViewTraceData; + size_t* mViewTracePosition; + + // Spike event data storage + UINT32** mLiveClusterTime; + UINT8** mLiveClusterID; + size_t** mLiveClusterPosition; + UINT32** mViewClusterTime; + UINT8** mViewClusterID; + size_t** mViewClusterPosition; + + // Digital and serial event data storage + UINT32* mLiveEventTime; + UINT16* mLiveEventID; + size_t* mLiveEventPosition; + UINT32* mViewEventTime; + UINT16* mViewEventID; + size_t* mViewEventPosition; + + /**Retrieves the traces included in the time frame given by @p startTime and @p endTime. + * @param startTime begining of the time frame from which to retrieve the data, given in milisecond. + * @param endTime end of the time frame from which to retrieve the data, given in milisecond. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void retrieveData(long startTime, long endTime, QObject* initiator, long startTimeInRecordingUnits); + + /**Computes the total length of the document in miliseconds.*/ + virtual void computeRecordingLength(); + + /** Helper function that searches timestamps in buffer*/ + template + Array* getTimeStampedData(UINT32* timeBuffer, + T* dataBuffer, + size_t* bufferPosition, + long start, + long end); +}; + +#endif diff --git a/src/channeliconview.cpp b/src/channeliconview.cpp index 291127a..8a7f6e4 100644 --- a/src/channeliconview.cpp +++ b/src/channeliconview.cpp @@ -52,7 +52,7 @@ ChannelIconView::ChannelIconView(const QColor& backgroundColor, int gridX, int g int v; backgroundColor.getHsv(&h,&s,&v); QColor legendColor; - if (s <= 80 && v >= 240 || (s <= 40 && v >= 220)) + if ((s <= 80 && v >= 240) || (s <= 40 && v >= 220)) legendColor = Qt::black; else legendColor = Qt::white; @@ -82,6 +82,18 @@ ChannelIconView::~ChannelIconView() { } +QList ChannelIconView::findItems(const int id) const { + QList matchedItems; + + for(int row = 0; row < count(); row++) { + ChannelIconViewItem* channelItem = static_cast(item(row)); + if(channelItem->getID() == id) + matchedItems.append(channelItem); + } + return matchedItems; +} + + void ChannelIconView::slotRowInsered() { adjustSize(); @@ -91,14 +103,14 @@ void ChannelIconView::slotRowInsered() QMimeData* ChannelIconView::mimeData(const QList items) const { if (items.isEmpty()) - return 0; + return nullptr /*0*/; QMimeData* mimedata = new QMimeData(); QByteArray data; //For the moment just one item QDataStream stream(&data, QIODevice::WriteOnly); - Q_FOREACH(QListWidgetItem *item, items) { - stream << *item; + Q_FOREACH(QListWidgetItem* item, items) { + stream << *static_cast(item); } mimedata->setData("application/x-channeliconview", data); @@ -132,7 +144,7 @@ void ChannelIconView::wheelEvent ( QWheelEvent * event ) event->accept(); } -bool ChannelIconView::dropMimeData(int index, const QMimeData * mimeData, Qt::DropAction action) +bool ChannelIconView::dropMimeData(int index, const QMimeData* mimeData, Qt::DropAction action) { Q_UNUSED(action); @@ -148,22 +160,19 @@ bool ChannelIconView::dropMimeData(int index, const QMimeData * mimeData, Qt::Dr if (data.isEmpty()) return false; - qDebug()<<" index "<data("application/x-channeliconview-number-item").toInt(); const QString sourceGroupName = QString::fromUtf8(mimeData->data("application/x-channeliconview-name")); - QDataStream stream(data); const bool moveAllGroup = (mimeData->data("application/x-channeliconview-move-all-channels") == "true"); - const int numberOfItems = mimeData->data("application/x-channeliconview-number-item").toInt(); if (sourceGroupName!= objectName()) { //TODO this part is buggy QList channelIds; for (int i=0; i< numberOfItems; ++i) { - ChannelIconViewItem *item = new ChannelIconViewItem(this); - stream >> *item; - channelIds.append(item->text().toInt()); - qDebug()<<" channelIds"<> sentItem; + channelIds.append(sentItem.getID()); } if (!channelIds.isEmpty()) { emit moveListItem(channelIds, sourceGroupName, objectName(), index, moveAllGroup); @@ -177,10 +186,9 @@ bool ChannelIconView::dropMimeData(int index, const QMimeData * mimeData, Qt::Dr QList channelIds; for (int i=0; i< numberOfItems; ++i) { - ChannelIconViewItem *item = new ChannelIconViewItem(this); - stream >> *item; - channelIds.append(item->text().toInt()); - delete item; + ChannelIconViewItem sentItem(this); + stream >> sentItem; + channelIds.append(sentItem.getID()); } QListWidgetItem *posItem = item(index); @@ -197,7 +205,7 @@ void ChannelIconView::mousePressEvent(QMouseEvent* event) { //If the user did not clicked on an item, ignore the click QListWidgetItem* item = itemAt(event->pos()); - if (item == 0L) + if (item == nullptr/*0L*/) return; if (event->button() == Qt::MiddleButton) { diff --git a/src/channeliconview.h b/src/channeliconview.h index 66c4443..219497b 100644 --- a/src/channeliconview.h +++ b/src/channeliconview.h @@ -34,30 +34,42 @@ *@author Lynn Hazan */ class ChannelIconViewItem : public QListWidgetItem { +// TODO: Use Qt type() functionality, to make this cleaner. public: ChannelIconViewItem( QListWidget *view = 0) : QListWidgetItem(view) { + // Unknown id + setData(Qt::UserRole, -1); // Drop between items, not onto items setFlags(flags() | (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled)); } - ChannelIconViewItem(const QIcon &icon, const QString &text, QListWidget *view = 0) + ChannelIconViewItem(const QIcon &icon, const QString &text, int id, QListWidget *view = 0) : QListWidgetItem(icon, text, view) { + // Save id under user role + setData(Qt::UserRole, id); // Drop between items, not onto items setFlags(flags() | (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled)); } + int getID() { + return data(Qt::UserRole).toInt(); + } }; + class ChannelIconView : public QListWidget { Q_OBJECT public: explicit ChannelIconView(const QColor& backgroundColor,int gridX,int gridY,bool edit,QWidget* parent = 0,const QString& name = QString()); ~ChannelIconView(); + // Like the findItems for labels but finds item by id. + QList findItems(const int id) const; + void setNewWidth(int width); QSize sizeHint() const; diff --git a/src/channelpalette.cpp b/src/channelpalette.cpp index 517c3ed..1d02a2c 100644 --- a/src/channelpalette.cpp +++ b/src/channelpalette.cpp @@ -40,7 +40,6 @@ #include #include - ChannelPalette::ChannelPalette(PaletteType type,const QColor& backgroundColor,bool edition,QWidget* parent,const char* name) : QScrollArea(parent) ,channelColors(0L) @@ -49,6 +48,7 @@ ChannelPalette::ChannelPalette(PaletteType type,const QColor& backgroundColor,bo spaceWidget(0L) ,channelsGroups(0L) ,groupsChannels(0L) + ,channelLabels(0L) ,greyScale(false) ,isGroupToRemove(false) ,type(type) @@ -82,8 +82,12 @@ ChannelPalette::ChannelPalette(PaletteType type,const QColor& backgroundColor,bo setWidget(w); verticalContainer->setSpacing(5); + // Find out how many pixels our 8 point font occupies. QFont f("Helvetica",8); QFontInfo fontInfo = QFontInfo(f); + // 2 = Max. Num. of Digits in Group ID. + // Then again I am not sure why the font height is used for the width. + // It works because this value is a proportional to the ppi of the screen. labelSize = fontInfo.pixelSize() * 2; //Set the legend in the good language @@ -114,7 +118,7 @@ void ChannelPalette::setGreyScale(bool grey){ int groupId = (*channelsGroups)[iterator.key()]; iconView = iconviewDict[QString::number(groupId)]; QListWidgetItem * item = 0L; - QListlstItem = iconView->findItems(QString::number(iterator.key()),Qt::MatchExactly); + QList lstItem = iconView->findItems(iterator.key()); if(!lstItem.isEmpty()) item = lstItem.first(); else @@ -153,11 +157,12 @@ void ChannelPalette::resizeEvent(QResizeEvent* event){ emit paletteResized(viewport()->width(),labelSize); } -void ChannelPalette::createChannelLists(ChannelColors* channelColors,QMap >* groupsChannels,QMap* channelsGroups){ +void ChannelPalette::createChannelLists(ChannelColors* channelColors,QMap >* groupsChannels,QMap* channelsGroups, QStringList* channelLabels){ //Assign the channelColors, groupsChannels and channelsGroups for future use. this->channelColors = channelColors; this->groupsChannels = groupsChannels; this->channelsGroups = channelsGroups; + this->channelLabels = channelLabels; //Create the iconViews QMap >::ConstIterator iterator; @@ -185,16 +190,17 @@ void ChannelPalette::setChannelLists() QString groupId = QString::number(iterator.key()); QList channelList = iterator.value(); for(uint i = 0; icolor(channelList.at(i)); + QColor color = channelColors->color(channelId); drawItem(painter,&pixmap,color,false,false); QIcon icon(pixmap); - new ChannelIconViewItem(icon,(QString::number(channelList.at(i))),iconviewDict[groupId]); + new ChannelIconViewItem(icon,channelLabels->at(channelId),channelId,iconviewDict[groupId]); } } } @@ -255,9 +261,9 @@ const QList ChannelPalette::selectedChannels(){ iteratordict.next(); const int count(iteratordict.value()->count()); for(int i = 0; i < count; ++i) { - QListWidgetItem* item = iteratordict.value()->item(i); + ChannelIconViewItem *item = static_cast(iteratordict.value()->item(i)); if(item->isSelected()){ - selectedChannels.append(item->text().toInt()); + selectedChannels.append(item->getID()); } } @@ -309,7 +315,7 @@ void ChannelPalette::hideUnselectAllChannels(){ //Update the pixmap int groupId = (*channelsGroups)[iterator.key()]; iconView = iconviewDict[QString::number(groupId)]; - QList lstItem = iconView->findItems(QString::number(iterator.key()),Qt::MatchExactly); + QList lstItem = iconView->findItems(iterator.key()); if(!lstItem.isEmpty()) { //Add an item to the target group with the same text but an update icon. QPixmap pixmap(14,14); @@ -358,7 +364,8 @@ void ChannelPalette::updateShowHideStatus(const QList& channelIds,bool show int groupId = (*channelsGroups)[*channelIterator]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(*channelIterator),Qt::MatchExactly); + + QListlstItem = iconView->findItems(*channelIterator); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); bool selected; @@ -376,6 +383,7 @@ void ChannelPalette::updateShowHideStatus(const QList& channelIds,bool show if(selected) selectedIds.append(*channelIterator); } + } @@ -428,7 +436,7 @@ void ChannelPalette::updateSkipStatus(const QMap& skipStatus){ int groupId = (*channelsGroups)[channelId]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(channelId),Qt::MatchExactly); + QList lstItem = iconView->findItems(channelId); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); bool selected = item->isSelected(); @@ -490,7 +498,7 @@ void ChannelPalette::updateSkipStatus(const QList&channelIds,bool skipStatu int groupId = (*channelsGroups)[*channelIterator]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(*channelIterator),Qt::MatchExactly); + QList lstItem = iconView->findItems(*channelIterator); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); bool selected = item->isSelected(); @@ -538,7 +546,7 @@ void ChannelPalette::updateColor(const QList &channelIds){ for(channelIterator = channelIds.begin(); channelIterator != channelIds.end(); ++channelIterator){ int groupId = (*channelsGroups)[*channelIterator]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(*channelIterator),Qt::MatchExactly); + QList lstItem = iconView->findItems(*channelIterator); if(lstItem.isEmpty()) return; //Get the channelColor associated with the item @@ -559,7 +567,7 @@ void ChannelPalette::updateColor(int channelId){ int groupId = (*channelsGroups)[channelId]; ChannelIconView* iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(channelId),Qt::MatchExactly); + QList lstItem = iconView->findItems(channelId); if(lstItem.isEmpty()) return; @@ -588,10 +596,10 @@ void ChannelPalette::updateGroupColor(int channelId){ ChannelIconView* iconView = iconviewDict[QString::number(groupId)]; for(int i = 0; icount();++i) { - QListWidgetItem* item = iconView->item(i); + ChannelIconViewItem* item = static_cast(iconView->item(i)); //Update the icon QPixmap pixmap(14,14); - drawItem(painter,&pixmap,color,channelsShowHideStatus[item->text().toInt()],channelsSkipStatus[item->text().toInt()]); + drawItem(painter,&pixmap,color,channelsShowHideStatus[item->getID()],channelsSkipStatus[item->getID()]); item->setIcon(QIcon(pixmap)); } } @@ -603,7 +611,7 @@ void ChannelPalette::applyGroupColor(PaletteType paletteType){ for(iterator = channelsGroups->begin(); iterator != channelsGroups->end(); ++iterator){ int groupId = (*channelsGroups)[iterator.key()]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(iterator.key()),Qt::MatchExactly); + QListlstItem = iconView->findItems(iterator.key()); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); //Get the channelColor associated with the item @@ -632,7 +640,7 @@ void ChannelPalette::applyCustomColor(){ for(iterator = channelsGroups->begin(); iterator != channelsGroups->end(); ++iterator){ int groupId = (*channelsGroups)[iterator.key()]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(iterator.key()),Qt::MatchExactly); + QList lstItem = iconView->findItems(iterator.key()); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); @@ -691,7 +699,7 @@ void ChannelPalette::changeBackgroundColor(const QColor &color){ for(groupIterator = channelsGroups->begin(); groupIterator != channelsGroups->end(); ++groupIterator){ int groupId = (*channelsGroups)[groupIterator.key()]; iconView = iconviewDict[QString::number(groupId)]; - QList lstItem = iconView->findItems(QString::number(groupIterator.key()),Qt::MatchExactly); + QList lstItem = iconView->findItems(groupIterator.key()); if(!lstItem.isEmpty()) { //update the color of the skip channels @@ -711,12 +719,11 @@ void ChannelPalette::changeBackgroundColor(const QColor &color){ update(); } -void ChannelPalette::changeColor(QListWidgetItem* item,bool single){ - int id = item->text().toInt(); +void ChannelPalette::changeColor(QListWidgetItem* item, bool single){ + int id = static_cast(item)->getID(); //Get the channelColor associated with the item const QColor oldColor = channelColors->color(id); - qDebug()<<" void ChannelPalette::changeColor(QListWidgetItem* item,bool single){"; QColor color = QColorDialog::getColor(oldColor,0); if(color.isValid()){ if(single){ @@ -734,21 +741,21 @@ void ChannelPalette::changeColor(QListWidgetItem* item,bool single){ } else{ //Change the color for all the other channels of the current group, depending on the PaletteType. - ChannelIconView* iconViewParent = static_cast(item->listWidget()); + ChannelIconView* iconViewParent = static_cast(item->listWidget()); for(int i = 0; icount();++i) { - QListWidgetItem *current = iconViewParent->item(i); + ChannelIconViewItem* current = static_cast(iconViewParent->item(i)); //Update the colors for the channel (color and group color) if(type == DISPLAY) - channelColors->setGroupColor(current->text().toInt(),color); + channelColors->setGroupColor(current->getID(),color); else - channelColors->setSpikeGroupColor(current->text().toInt(),color); - if(!channelsSkipStatus[current->text().toInt()]) - channelColors->setColor(current->text().toInt(),color); + channelColors->setSpikeGroupColor(current->getID(),color); + if(!channelsSkipStatus[current->getID()]) + channelColors->setColor(current->getID(),color); //Update the icon QPixmap pixmap(14,14); QPainter painter; - drawItem(painter,&pixmap,color,channelsShowHideStatus[current->text().toInt()],channelsSkipStatus[current->text().toInt()]); + drawItem(painter,&pixmap,color,channelsShowHideStatus[current->getID()],channelsSkipStatus[current->getID()]); current->setIcon(QIcon(pixmap)); } const QString groupId = iconViewParent->objectName(); @@ -784,7 +791,7 @@ void ChannelPalette::selectChannels(const QList& selectedChannels){ for(channelIterator = selectedChannels.begin(); channelIterator != selectedChannels.end(); ++channelIterator){ int groupId = (*channelsGroups)[*channelIterator]; iconView = iconviewDict[QString::number(groupId)]; - QList lstItem = iconView->findItems(QString::number(*channelIterator),Qt::MatchExactly); + QList lstItem = iconView->findItems(*channelIterator); if(!lstItem.isEmpty()) { currentIcon = lstItem.first(); currentIcon->setSelected(true); @@ -831,7 +838,22 @@ void ChannelPalette::createGroup(int id){ label->setFont(f); label->adjustSize(); - ChannelIconView* iconView = new ChannelIconView(backgroundColor,labelSize+7,15*2,edit,group,QString::number(id)); + // Find max label length + int maxLabelLength = 0; + QFontMetrics fm(QFont("Helvetica",8)); + for(int i = 0; i < channelLabels->size(); i++) { + int currentLength = fm.width(channelLabels->at(i)); + if(currentLength > maxLabelLength) + maxLabelLength = currentLength; + } + + // Construct grid size based on icon size and text height and width. + int padding = 10; + int iconSize = 14; + int gridX = std::max(maxLabelLength, iconSize) + padding; + int gridY = iconSize + (labelSize / 2) + padding; + + ChannelIconView* iconView = new ChannelIconView(backgroundColor,gridX,gridY,edit,group,QString::number(id)); iconView->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); if(iconviewDict.count() >= 1){ if(iconviewDict.contains("1") ){ @@ -841,8 +863,10 @@ void ChannelPalette::createGroup(int id){ else if(iconviewDict.contains("0")) iconView->resize((iconviewDict["0"])->size().width(),labelSize); //In the spike palette at the begining only the spikeTrashGroup (gpId-1) exists. - else - iconView->resize((iconviewDict["-1"])->size().width(),labelSize); + else { + if(iconviewDict.contains("-1")) + iconView->resize((iconviewDict["-1"])->size().width(),labelSize); + } } else iconView->adjustSize(); @@ -1119,8 +1143,9 @@ void ChannelPalette::moveChannels(int targetGroup){ QList currentMovedChannels; QList channelIds; for(int i = 0; i count();++i) { - QListWidgetItem *item = it.value()->item(i); - int channelId = item->text().toInt(); + ChannelIconViewItem* item = static_cast(it.value()->item(i)); + int channelId = item->getID(); + QString label = item->text(); if(item->isSelected()){ //if the source is the trash group, keep track of it to inform the other palette if(it.key() == "0") @@ -1132,7 +1157,7 @@ void ChannelPalette::moveChannels(int targetGroup){ QPixmap pixmap(14,14); QColor color = channelColors->color(channelId); drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); - new ChannelIconViewItem(QIcon(pixmap),QString::number(channelId),iconView); + new ChannelIconViewItem(QIcon(pixmap),label,channelId,iconView); //Modify the entry in the map channels-group channelsGroups->remove(channelId); @@ -1144,7 +1169,7 @@ void ChannelPalette::moveChannels(int targetGroup){ //Delete the entries in the source group QList::iterator it2; for(it2 = currentMovedChannels.begin(); it2 != currentMovedChannels.end(); ++it2){ - QListlstItem = it.value()->findItems(QString::number(*it2),Qt::MatchExactly); + QListlstItem = it.value()->findItems(*it2); if(!lstItem.isEmpty()) { delete lstItem.first(); } @@ -1158,7 +1183,7 @@ void ChannelPalette::moveChannels(int targetGroup){ //Add/update the group entry in the map group-channel list QList targetChannels; for(int i = 0; icount();++i) { - targetChannels.append(iconView->item(i)->text().toInt()); + targetChannels.append(static_cast(iconView->item(i))->getID()); } groupsChannels->insert(targetGroup,targetChannels); @@ -1348,22 +1373,21 @@ void ChannelPalette::moveChannels(const QList& channelIds, const QString &s for(iterator = channelIds.begin(); iterator != channelIds.end(); ++iterator){ //Delete the item from the sourceGroup - QListlstItem = sourceIconView->findItems(QString::number(*iterator),Qt::MatchExactly); + QListlstItem = sourceIconView->findItems(*iterator); if(!lstItem.isEmpty()) { delete lstItem.first(); //Add an item to the target group. QPixmap pixmap(14,14); - QColor color = channelColors->color(*iterator); + QColor color = channelColors->color(*iterator); if(targetGroup == "0") channelsShowHideStatus[*iterator] = false; QPainter painter; drawItem(painter,&pixmap,color,channelsShowHideStatus[*iterator],channelsSkipStatus[*iterator]); - qDebug()<<" index "<at(*iterator),*iterator); targetIconView->insertItem(index, item); } else { - new ChannelIconViewItem(QIcon(pixmap),QString::number(*iterator), targetIconView); + new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(*iterator),*iterator, targetIconView); } //Update the group color if(type == DISPLAY) @@ -1412,16 +1436,14 @@ void ChannelPalette::moveChannels(const QList& channelIds, const QString &s } void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetItem* after){ - qDebug()<<" void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetItem* after){"<text(); + afterId = static_cast(after)->getID(); } } @@ -1475,8 +1497,8 @@ void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetIt QList channelIds; ChannelIconView* iconView = iconviewDict[QString::number(gpId)]; for(int i = 0; i count();++i) { - QListWidgetItem *item = iconView->item(i); - int channelId = item->text().toInt(); + ChannelIconViewItem *item = static_cast(iconView->item(i)); + int channelId = item->getID(); if(item->isSelected()){ movedChannels.append(channelId); @@ -1489,7 +1511,7 @@ void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetIt channelsShowHideStatus[channelId] = false; drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); int index = targetIconView->row(after); - after = new ChannelIconViewItem(QIcon(pixmap),QString::number(channelId)); + after = new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(channelId),channelId); targetIconView->insertItem(index+1,after); i = index + 1; @@ -1504,7 +1526,7 @@ void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetIt //Delete the entries in the source group QList::iterator it; for(it = currentMovedChannels.begin(); it != currentMovedChannels.end(); ++it){ - QListlstItem = iconView->findItems(QString::number(*it),Qt::MatchExactly); + QListlstItem = iconView->findItems(*it); if(!lstItem.isEmpty()) { delete lstItem.first(); } @@ -1525,24 +1547,24 @@ void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetIt } if(moveFirst){ - QListWidgetItem* first = targetIconView->item(0); - QString channelId = first->text(); + ChannelIconViewItem* first = static_cast(targetIconView->item(0)); + int channelId = first->getID(); delete first; //Add a new item corresponding to the channel Id. QPixmap pixmap(14,14); - QColor color = channelColors->color(channelId.toInt()); - drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId.toInt()],channelsSkipStatus[channelId.toInt()]); - ChannelIconViewItem *item = new ChannelIconViewItem(QIcon(pixmap),channelId); + QColor color = channelColors->color(channelId); + drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); + ChannelIconViewItem *item = new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(channelId),channelId); targetIconView->insertItem(targetIconView->row(after)+1,item); } //Modify the entry in the map group-channel list QList targetChannels; for(int i = 0; i < targetIconView->count();++i) { - QListWidgetItem *item = targetIconView->item(i); - targetChannels.append(item->text().toInt()); + ChannelIconViewItem *item = static_cast(targetIconView->item(i)); + targetChannels.append(item->getID()); } groupsChannels->remove(targetGroup.toInt()); @@ -1565,19 +1587,19 @@ void ChannelPalette::slotChannelsMoved(const QString &targetGroup, QListWidgetIt //If the channels have been moved to the trash inform the other palette. if(targetGroup == "0") { - emit channelsMovedToTrash(movedChannels,afterId,beforeFirst); + emit channelsMovedToTrash(movedChannels, afterId, beforeFirst); } update(); } -void ChannelPalette::trashChannelsMovedAround(const QList& channelIds, const QString &afterId, bool beforeFirst){ - QListWidgetItem* after = 0; +void ChannelPalette::trashChannelsMovedAround(const QList& channelIds, const int afterId, bool beforeFirst){ + QListWidgetItem* after = NULL; ChannelIconView* trash = iconviewDict["0"]; //If the items have to be moved before the first item, insert them after the first item //and then move the first item after the others if(!beforeFirst) { - QListlstItem = trash->findItems(afterId,Qt::MatchExactly); + QList lstItem = trash->findItems(afterId); if(!lstItem.isEmpty()) after = lstItem.first(); } @@ -1590,12 +1612,9 @@ void ChannelPalette::trashChannelsMovedAround(const QList& channelIds, cons void ChannelPalette::moveChannels(const QList& channelIds,const QString& sourceGroup,QListWidgetItem* after){ QList::const_iterator iterator; QPainter painter; - qDebug()<<" sourceGroup"<& channelIds "<& channelIds,const QString& so } int afterIndex = iconView->row(after); for(iterator = channelIds.begin(); iterator != channelIds.end(); ++iterator){ + int channelId = *iterator; //Delete the item corresponding to the channel Id - QListlstItem = iconView->findItems(QString::number(*iterator),Qt::MatchExactly); + QList lstItem = iconView->findItems(channelId); if(!lstItem.isEmpty()) { delete lstItem.first(); //Add a new item corresponding to the channel Id. QPixmap pixmap(14,14); - QColor color = channelColors->color(*iterator); - drawItem(painter,&pixmap,color,channelsShowHideStatus[*iterator],channelsSkipStatus[*iterator]); - qDebug()<<" afterIndex"<color(channelId); + drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); + after = new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(channelId),channelId); iconView->insertItem(afterIndex,after); afterIndex++; - } } if(moveFirst){ - QListWidgetItem* first = iconView->item(0); - const QString channelId = first->text(); + ChannelIconViewItem* first = static_cast(iconView->item(0)); + QString label = first->text(); + int channelId = first->getID(); delete first; //Add a new item corresponding to the channel Id. QPixmap pixmap(14,14); - QColor color = channelColors->color(channelId.toInt()); - drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId.toInt()],channelsSkipStatus[channelId.toInt()]); + QColor color = channelColors->color(channelId); + drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); const int afterIndex = iconView->row(after); - after = new ChannelIconViewItem(QIcon(pixmap),channelId); + after = new ChannelIconViewItem(QIcon(pixmap), label, channelId); iconView->insertItem(afterIndex,after); } //Modify the entry in the map group-channel list QList sourceChannels; for(int i=0; icount();++i) { - sourceChannels.append(iconView->item(i)->text().toInt()); + sourceChannels.append(static_cast(iconView->item(i))->getID()); } groupsChannels->remove(sourceGroup.toInt()); @@ -1647,15 +1666,15 @@ void ChannelPalette::moveChannels(const QList& channelIds,const QString& so void ChannelPalette::slotChannelsMoved(const QList& channelIds, const QString &sourceGroup, QListWidgetItem *after){ //If the channels have been moved around in the trash, inform the other palette. if(sourceGroup == "0" ){ - QString afterId; + int afterId = -1; bool beforeFirst = false; if(after == 0){ beforeFirst = true; } else { - afterId = after->text(); + afterId = static_cast(after)->getID(); } - emit channelsMovedAroundInTrash(channelIds,afterId,beforeFirst); + emit channelsMovedAroundInTrash(channelIds, afterId, beforeFirst); } //Actually move the channels @@ -1674,7 +1693,6 @@ void ChannelPalette::discardChannels() } void ChannelPalette::discardChannels(const QList& channelsToDiscard){ - qDebug()<<" void ChannelPalette::discardChannels(const QList& channelsToDiscard){ "<& channelsToDiscard){ //Trash if(it.key() == QLatin1String("0")) { for(int i=0; icount();++i) { - QListWidgetItem *item = it.value()->item(i); - const int channelId = item->text().toInt(); + ChannelIconViewItem *item = static_cast(it.value()->item(i)); + const int channelId = item->getID(); if(channelsToDiscard.contains(channelId)) { channelsShowHideStatus[channelId] = false; //Update the icon @@ -1723,8 +1741,8 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard){ QList currentDiscardedChannels; QList channelIds; for(int i=0; icount();++i) { - QListWidgetItem *item = it.value()->item(i); - int channelId = item->text().toInt(); + ChannelIconViewItem *item = static_cast(it.value()->item(i)); + int channelId = item->getID(); if(channelsToDiscard.contains(channelId)){ currentDiscardedChannels.append(channelId); @@ -1738,7 +1756,7 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard){ //Delete the entries in the source group QList::iterator it2; for(it2 = currentDiscardedChannels.begin(); it2 != currentDiscardedChannels.end(); ++it2){ - QListlstItem = it.value()->findItems(QString::number(*it2),Qt::MatchExactly); + QList lstItem = it.value()->findItems(*it2); if(!lstItem.isEmpty()) { delete lstItem.first(); } @@ -1758,7 +1776,7 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard){ QColor color = channelColors->color(*channelIterator); channelsShowHideStatus[*channelIterator] = false; drawItem(painter,&pixmap,color,false,channelsSkipStatus[*channelIterator]); - new ChannelIconViewItem(QIcon(pixmap),QString::number(*channelIterator),trash); + new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(*channelIterator),*channelIterator,trash); //Update the group color if(type == DISPLAY) @@ -1783,8 +1801,7 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard){ update(); } -void ChannelPalette::discardChannels(const QList& channelsToDiscard,const QString& afterId,bool beforeFirst){ - qDebug()<<" void ChannelPalette::discardChannels(const QList& channelsToDiscard,const QString& afterId,bool beforeFirst){"<& channelsToDiscard,const int afterId,bool beforeFirst){ QListWidgetItem* after = 0; ChannelIconView* trash = iconviewDict["0"]; //If the items have to be moved before the first item, insert them after the first item @@ -1794,11 +1811,10 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard,const Q after = trash->item(0); moveFirst = true; } else { - QList lstItem = trash->findItems(afterId,Qt::MatchExactly); + QList lstItem = trash->findItems(afterId); if(!lstItem.isEmpty()) { after = lstItem.first(); } - qDebug() <<" after "<& channelsToDiscard,const Q int groupId = (*channelsGroups)[*channelIterator]; QList sourceChannels = (*groupsChannels)[groupId]; iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(*channelIterator),Qt::MatchExactly); + QList lstItem = iconView->findItems(*channelIterator); if(!lstItem.isEmpty()) { delete lstItem.first(); } @@ -1831,7 +1847,7 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard,const Q drawItem(painter,&pixmap,color,false,channelsSkipStatus[*channelIterator]); const int index = trash->row(after); - ChannelIconViewItem *newItem = new ChannelIconViewItem(QIcon(pixmap),QString::number(*channelIterator)); + ChannelIconViewItem *newItem = new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(*channelIterator),*channelIterator); trash->insertItem(index+1, newItem); @@ -1854,24 +1870,25 @@ void ChannelPalette::discardChannels(const QList& channelsToDiscard,const Q } if(moveFirst){ - QListWidgetItem* first = trash->item(0); - QString channelId = first->text(); + ChannelIconViewItem* first = static_cast(trash->item(0)); + QString label = first->text(); + int channelId = first->getID(); delete first; //Add a new item corresponding to the channel Id. QPixmap pixmap(14,14); - QColor color = channelColors->color(channelId.toInt()); - drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId.toInt()],channelsSkipStatus[channelId.toInt()]); + QColor color = channelColors->color(channelId); + drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); const int index = trash->row(after); - after = new ChannelIconViewItem(QIcon(pixmap),(channelId)); + after = new ChannelIconViewItem(QIcon(pixmap), label, channelId); trash->insertItem(index+1,after); } //Modify the entry in the map group-channel list QList trashChannels; for(int i = 0; i count();++i) { - QListWidgetItem *item = trash->item(i); - trashChannels.append(item->text().toInt()); + ChannelIconViewItem* item = static_cast(trash->item(i)); + trashChannels.append(item->getID()); } groupsChannels->remove(0); groupsChannels->insert(0,trashChannels); @@ -1897,8 +1914,6 @@ void ChannelPalette::setEditMode(bool edition){ if(edition) channelsShowHideStatus.clear(); - qDebug()<<" channelsShowHideStatus"<::Iterator iterator; @@ -1907,7 +1922,7 @@ void ChannelPalette::setEditMode(bool edition){ for(iterator = channelsGroups->begin(); iterator != channelsGroups->end(); ++iterator){ const int groupId = (*channelsGroups)[iterator.key()]; ChannelIconView* iconView = iconviewDict[QString::number(groupId)]; - QListlstItem = iconView->findItems(QString::number(iterator.key()),Qt::MatchExactly); + QList lstItem = iconView->findItems(iterator.key()); if(!lstItem.isEmpty()) { QListWidgetItem *item = lstItem.first(); bool selected = false; @@ -1916,7 +1931,6 @@ void ChannelPalette::setEditMode(bool edition){ channelsShowHideStatus.insert(iterator.key(),selected); } else { selected = channelsShowHideStatus[iterator.key()]; - qDebug()<<" iterator.key()"<icon(); QPixmap pixmap(icon.pixmap(QSize(14,14)).size()); @@ -1929,7 +1943,6 @@ void ChannelPalette::setEditMode(bool edition){ } selectChannels(selectedIds); - qDebug()<<"void ChannelPalette::setEditMode "<count();++i ) { - QListWidgetItem *item = it.value()->item(i); - int channelId = item->text().toInt(); + ChannelIconViewItem* item = static_cast(it.value()->item(i)); + int channelId = item->getID(); if(trashChannels.contains(channelId)) continue; @@ -2078,8 +2091,9 @@ void ChannelPalette::trashChannels(int destinationGroup){ QList currentDiscardedChannels; QList channelIds; for(int i =0; icount();++i) { - QListWidgetItem *item = it.value()->item(i); - const int channelId = item->text().toInt(); + ChannelIconViewItem* item = static_cast(it.value()->item(i)); + QString label = item->text(); + const int channelId = item->getID(); if(item->isSelected()){ discardedChannels.append(channelId); trashChannels.append(channelId); @@ -2090,7 +2104,7 @@ void ChannelPalette::trashChannels(int destinationGroup){ if(destinationGroup == 0) channelsShowHideStatus[channelId] = false; drawItem(painter,&pixmap,color,channelsShowHideStatus[channelId],channelsSkipStatus[channelId]); - new ChannelIconViewItem(QIcon(pixmap),QString::number(channelId),trash); + new ChannelIconViewItem(QIcon(pixmap),label,channelId,trash); //Modify the entry in the map channels-group channelsGroups->remove(channelId); @@ -2102,7 +2116,7 @@ void ChannelPalette::trashChannels(int destinationGroup){ //Delete the entries in the source group QList::iterator it2; for(it2 = currentDiscardedChannels.begin(); it2 != currentDiscardedChannels.end(); ++it2){ - QList lstItem = it.value()->findItems(QString::number(*it2),Qt::MatchExactly); + QList lstItem = it.value()->findItems(*it2); if(!lstItem.isEmpty()) delete lstItem.first(); } @@ -2148,7 +2162,7 @@ void ChannelPalette::trashChannels(int destinationGroup){ void ChannelPalette::slotMoveListItem(const QList &items, const QString& sourceGroup,const QString& destinationGroup,int index, bool moveAll) { - QString afterId; + int afterId; bool beforeFirst = false; if(destinationGroup == QLatin1String("0") ){ if(index == 0){ @@ -2157,10 +2171,10 @@ void ChannelPalette::slotMoveListItem(const QList &items, const QString& so ChannelIconView* trash = iconviewDict["0"]; QListWidgetItem *item = trash->item(index); if (item) - afterId = item->text(); + afterId = static_cast(item)->getID(); } - emit channelsMovedToTrash(items,afterId,beforeFirst); + emit channelsMovedToTrash(items, afterId, beforeFirst); } else if ( sourceGroup == QLatin1String("0") ){ if(index == 0){ beforeFirst = true; @@ -2168,7 +2182,7 @@ void ChannelPalette::slotMoveListItem(const QList &items, const QString& so ChannelIconView* iconView = iconviewDict[destinationGroup]; QListWidgetItem *item = iconView->item(index); if (item) - afterId = item->text(); + afterId = static_cast(item)->getID(); } emit channelsMovedAroundInTrash(items, afterId, beforeFirst); @@ -2203,7 +2217,7 @@ void ChannelPalette::dragChannels(const QList& channelIds, const QString &s for(iterator = channelIds.begin(); iterator != channelIds.end(); ++iterator){ //Delete the item from the sourceGroup - QListlstItem = sourceIconView->findItems(QString::number(*iterator),Qt::MatchExactly); + QList lstItem = sourceIconView->findItems(*iterator); if(!lstItem.isEmpty()) { delete lstItem.first(); //Add an item to the target group. @@ -2214,12 +2228,12 @@ void ChannelPalette::dragChannels(const QList& channelIds, const QString &s QPainter painter; drawItem(painter,&pixmap,color,channelsShowHideStatus[*iterator],channelsSkipStatus[*iterator]); if (index!=-1) { - ChannelIconViewItem *item = new ChannelIconViewItem(QIcon(pixmap),QString::number(*iterator)); + ChannelIconViewItem *item = new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(*iterator),*iterator); targetIconView->insertItem(index, item); targetChannels.insert(index,*iterator); index++; } else { - new ChannelIconViewItem(QIcon(pixmap),QString::number(*iterator), targetIconView); + new ChannelIconViewItem(QIcon(pixmap),channelLabels->at(*iterator),*iterator, targetIconView); targetChannels.append(*iterator); } //Update the group color @@ -2313,4 +2327,3 @@ void SpaceWidget::dragEnterEvent(QDragEnterEvent *event) event->acceptProposedAction(); } } - diff --git a/src/channelpalette.h b/src/channelpalette.h index caf9e12..0d5d86e 100644 --- a/src/channelpalette.h +++ b/src/channelpalette.h @@ -77,7 +77,7 @@ class ChannelPalette : public QScrollArea * @param groupsChannels map given the list of channels for each group. * @param channelsGroups map given to which group each channel belongs. */ - void createChannelLists(ChannelColors* channelColors,QMap >* groupsChannels,QMap* channelsGroups); + void createChannelLists(ChannelColors* channelColors,QMap >* groupsChannels,QMap* channelsGroups, QStringList* channelLabels); /**Selects the channels contain in @p selectedChannels. * @param selectedChannels list of channels to select. @@ -121,10 +121,10 @@ public Q_SLOTS: void slotChannelsMoved(const QString& targetGroup, QListWidgetItem *after); /**Channels moved around in a group.*/ void slotChannelsMoved(const QList& channelIds,const QString& sourceGroup,QListWidgetItem* after); - void trashChannelsMovedAround(const QList& channelIds,const QString& afterId,bool beforeFirst); + void trashChannelsMovedAround(const QList& channelIds,const int afterId,bool beforeFirst); void discardChannels(); void discardChannels(const QList& channelsToDiscard); - void discardChannels(const QList& channelsToDiscard, const QString &afterId, bool beforeFirst); + void discardChannels(const QList& channelsToDiscard, const int afterID, bool beforeFirst); void discardSpikeChannels(); void showChannels(); void hideChannels(); @@ -174,8 +174,8 @@ protected Q_SLOTS: void channelsDiscarded(const QList& discarded); void setDragAndDrop(bool dragAndDrop); void groupModified(); - void channelsMovedToTrash(const QList &channelIds, const QString &afterId,bool beforeFirst); - void channelsMovedAroundInTrash(const QList& channelsToDiscard,QString afterId,bool beforeFirst); + void channelsMovedToTrash(const QList &channelIds, const int afterId,bool beforeFirst); + void channelsMovedAroundInTrash(const QList& channelsToDiscard,int afterId,bool beforeFirst); void channelsRemovedFromTrash(const QList& channelIds); void channelsSelected(const QList& selectedChannels); @@ -209,6 +209,9 @@ protected Q_SLOTS: */ QMap >* groupsChannels; + /** Mapping between channel id and label */ + QStringList* channelLabels; + int labelSize; /**True if the the colors are in grey-scale*/ diff --git a/src/clusterproperties.h b/src/clusterproperties.h index 956a5b6..15e47ad 100644 --- a/src/clusterproperties.h +++ b/src/clusterproperties.h @@ -25,7 +25,7 @@ #include //include files for the application -#include +#include "clusterpropertieslayout.h" /** Class representing the NeuroScope cluster preferences tab contained in NeuroScope Defaults Configuration page of the Neuroscope preferences dialog and in the properties dialog. *@author Lynn Hazan diff --git a/src/clustersprovider.cpp b/src/clustersprovider.cpp index de12723..e70bf52 100644 --- a/src/clustersprovider.cpp +++ b/src/clustersprovider.cpp @@ -68,7 +68,7 @@ int ClustersProvider::loadData(){ //Get the number of spikes nbSpikes = Utilities::getNbLines(timeFilePath); - qDebug()<<"nbSpikes "< +#include "dataprovider.h" #include #include @@ -58,7 +58,7 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. */ - void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); + virtual void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); /**Looks up for the first of the clusters included in the list @p selectedIds existing after the time @p startTime. @@ -70,7 +70,7 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits starting time, in recording units, for the look up. */ - void requestNextClusterData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator,long startTimeInRecordingUnits); + virtual void requestNextClusterData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator,long startTimeInRecordingUnits); /**Looks up for the first of the clusters included in the list @p selectedIds existing before the time @p endTime. @@ -82,12 +82,12 @@ class ClustersProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits starting time, in recording units, for the look up. */ - void requestPreviousClusterData(long startTime,long timeFrame,QList selectedIds,QObject* initiator,long startTimeInRecordingUnits); - + virtual void requestPreviousClusterData(long startTime,long timeFrame,QList selectedIds,QObject* initiator,long startTimeInRecordingUnits); + /**Loads the cluster ids and the corresponding spike time. * @return an loadReturnMessage enum giving the load status */ - int loadData(); + virtual int loadData(); /**Returns list of cluster Ids. * @return */ @@ -164,7 +164,7 @@ class ClustersProvider : public DataProvider { */ void previousClusterDataReady(Array& data,QObject* initiator,QString providerName,long startingTime,long startingTimeInRecordingUnits); -private: +protected: /**Provider's name.*/ QString name; diff --git a/src/config-neuroscope.h b/src/config-neuroscope.h new file mode 100644 index 0000000..34d87c1 --- /dev/null +++ b/src/config-neuroscope.h @@ -0,0 +1,5 @@ +#define NEUROSCOPE_VERSION "2.1.0" +//#define NEUROSCOPE_DOC_PATH "${CMAKE_INSTALL_PREFIX}/share/doc/neuroscope/HTML/en/" +//#define NEUROSCOPE_DOC_PATH "D:/Gigs/Neurosuite5/neuroscope/doc/en/html/" +#define NEUROSCOPE_DOC_PATH "/doc/en/html/" + diff --git a/src/configuration.cpp b/src/configuration.cpp index e8d1e95..160ecaf 100644 --- a/src/configuration.cpp +++ b/src/configuration.cpp @@ -21,7 +21,7 @@ #include "configuration.h" -const float Configuration::screenGainDefault = 0.2; //half theta amplitude (0.4 ms) +const float Configuration::screenGainDefault = 0.2f; //half theta amplitude (0.4 ms) const int Configuration::voltageRangeDefault = 20;//+-10 volts const int Configuration::amplificationDefault = 1000; const int Configuration::nbChannelsDefault = 32; diff --git a/src/eventsprovider.cpp b/src/eventsprovider.cpp index 8cc6162..c3d039f 100644 --- a/src/eventsprovider.cpp +++ b/src/eventsprovider.cpp @@ -48,7 +48,7 @@ EventsProvider::EventsProvider(const QString &fileUrl, double currentSamplingRat } EventsProvider::~EventsProvider(){ - qDebug()<<"in ~EventsProvider "<(floor(0.5 + timeStamps(1,nbEvents))); + fileMaxTime = previousEndTime; + + return OK; +} + + +void EventsProvider::updateMappingAndDescriptionLength() { //Assign an id to each event description //The iterator iterates on the keys sorted QMap::Iterator iterator; @@ -140,15 +156,6 @@ int EventsProvider::loadData(){ descriptionLength = static_cast(qMin((mean + stdVar),maxSize)); //Be sure that the length is minimum 2 digits descriptionLength = qMax(descriptionLength,2); - - //Initialize the variables - previousStartTime = 0; - previousStartIndex = 1; - previousEndIndex = nbEvents; - previousEndTime = static_cast(floor(0.5 + timeStamps(1,nbEvents))); - fileMaxTime = previousEndTime; - - return OK; } void EventsProvider::initializeEmptyProvider(){ @@ -171,7 +178,137 @@ void EventsProvider::requestData(long startTime,long endTime,QObject* initiator, retrieveData(startTime,endTime,initiator); } +/// +/// \brief bBoundsChanged +/// \param lower_bound +/// \param upper_bound +/// \return +/// function to return true if the two input parameters differ from the previous call +bool bBoundsChanged(long lower_bound, long upper_bound) +{ + static long prev_lower; + static long prev_upper; + + if ((prev_lower==lower_bound) && (prev_upper==upper_bound)) + return false; + + prev_lower = lower_bound; + prev_upper = upper_bound; + return true; +} + +/// +/// \brief lGetLowerBound() Return the first index location in Array A that falls after the lTarget value +/// \param lower_bound index for first position in Array to search +/// \param upper_bound index for last position in Array to search +/// \param lTarget start time for window +/// \param A +/// \return +///Return the first index location in Array A that falls after the lTarget value +/// We assume that A is sorted in ascending order +/// A bisection search method is used. +/// Note: we multiply the values in A by 1000 to get milliseconds. They may be comming in as seconds? +long lGetLowerBound(long lower_bound, long upper_bound, long lTarget, Array &A) +{ + bBoundsChanged(lower_bound, upper_bound); // initialize some static variables + + while ((lower_bound < upper_bound)) + { + long mid = (upper_bound + lower_bound)/2; + if (lTarget <= A(1,mid)*1000) { + upper_bound = mid; + } + else { + lower_bound= mid+1; + } + + // if the boundaries do not change, then we are finished + if (!bBoundsChanged(lower_bound, upper_bound)) + break; + } + return upper_bound; +} + +long lGetUpperBound(long lower_bound, long upper_bound, long lTarget, Array &A) +{ + bBoundsChanged(lower_bound, upper_bound); // initialize some static variables + + while (lower_bound < upper_bound) + { + long mid = (lower_bound + upper_bound)/2 + 1; + if (lTarget < A(1,mid)*1000) + upper_bound = mid-1; + else + lower_bound = mid; + + // if the boundaries do not change, then we are finished + if (!bBoundsChanged(lower_bound, upper_bound)) + break; + } + return lower_bound; +} + void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator){ + + // This is a faster method that uses a bisection search + //long lLower = lGetLowerBound(1, nbEvents, startTime, timeStamps); + //long lUpper = lGetUpperBound(1, nbEvents, endTime, timeStamps); + //QList EventsInRange; + //for (long lIndex =lLower; lIndex <= lUpper; lIndex++) + // EventsInRange.append(lIndex); + //qDebug() << "window limits " << startTime << " " << endTime << "\n"; + //qDebug() << "index limits " << lLower << " " << lUpper << "\n"; + //for (int jj=1; jj <= nbEvents; ++jj) + // qDebug() << "time stamps " << jj << " " << timeStamps(1, jj) << " "; + // End of faster method + + + // Make a list of all event indices that fall inside the display window + // This is a slow search, but it fixed an old bug. + QList EventsInRange; + for (long lIndex =1; lIndex <= nbEvents; lIndex++) + { + // Convert from seconds to milliseconds using 1000 + long time = static_cast(floor(0.5 + timeStamps(1,lIndex)* 1000)); + if (time >= startTime && time <= endTime ) + { + EventsInRange.append(lIndex); + } + } + + long nSizeEventsInRange = EventsInRange.count(); + + //Store the information for the next request + previousStartTime = startTime; + previousStartIndex = (nSizeEventsInRange > 0) ? EventsInRange.first() : 1; + + previousEndTime = endTime; + previousEndIndex = (nSizeEventsInRange > 1) ? EventsInRange.last() : 1; + + // Now transfer the times that are in range to the stored list + + //look up for the event ids and indexes. + Array times; + Array ids; + times.setSize(1,nSizeEventsInRange); + ids.setSize(1, nSizeEventsInRange); + + long count = 0; + for (long lIndex : EventsInRange) + { + count++; + double dDiff = (timeStamps(1,lIndex)*1000 - static_cast(startTime))/1000.0; + times(1,count) = qMax(static_cast(floor(static_cast(0.5 + dDiff * currentSamplingRate))),0L); + //times(1,count + 1) = qMax(static_cast(floor(static_cast(0.5 +(timeStamps(1,startIndex) - static_cast(startTime)) * currentSamplingRate))),0L); + ids(1,count) = eventIds[events(1,lIndex)]; + } + + //Send the information to the receiver. + emit dataReady(times,ids,initiator,name); +} + +#ifdef TRASH +void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator){ Array times; Array ids; @@ -182,6 +319,9 @@ void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator } if(endTime > fileMaxTime) endTime = fileMaxTime; + // for (int jj=1; jj <= nbEvents; ++jj) + // qDebug() << "time stamps " << jj << " " << timeStamps(1, jj) << " "; + long startIndex = previousStartIndex; long endIndex = previousEndIndex; long dicotomyBreak = 1000; @@ -192,6 +332,7 @@ void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator if((startTime != previousStartTime) && (startTime != previousEndTime)){ if(startTime == 0){ startIndex = 1; + if(endTime <= previousStartTime) endIndex = previousStartIndex; else if(endTime <= previousEndTime) endIndex = previousEndIndex; else if(endTime > previousEndTime) endIndex = nbEvents; @@ -199,12 +340,14 @@ void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator if(startTime < previousStartTime){ startIndex = static_cast(previousStartIndex / 2); if(startIndex <= 0) startIndex = 1; + if(endTime <= previousStartTime) endIndex = previousStartIndex; else if(endTime <= previousEndTime) endIndex = previousEndIndex; else if(endTime > previousEndTime) endIndex = nbEvents; } else if(startTime < previousEndTime && startTime > previousStartTime){ startIndex = previousStartIndex + static_cast((previousEndIndex - previousStartIndex + 1)/ 2); + if(endTime <= previousEndTime) endIndex = previousEndIndex; else if(endTime > previousEndTime) endIndex = nbEvents; } @@ -228,7 +371,7 @@ void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator } newEndIndex = previousStart; } - else{ + else{ // time is < startTime newStartIndex = newStartIndex + ((newEndIndex - newStartIndex + 1) / 2); if(newStartIndex > nbEvents){ newStartIndex = nbEvents; @@ -328,6 +471,11 @@ void EventsProvider::retrieveData(long startTime,long endTime,QObject* initiator //Send the information to the receiver. emit dataReady(finalTimes,finalIds,initiator,name); } +#endif + + + + void EventsProvider::requestNextEventData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator){ long initialStartTime = startTime; diff --git a/src/eventsprovider.h b/src/eventsprovider.h index e11f7ee..5896e73 100644 --- a/src/eventsprovider.h +++ b/src/eventsprovider.h @@ -19,7 +19,7 @@ #define EVENTSPROVIDER_H //include files for the application -#include +#include "dataprovider.h" #include #include @@ -69,7 +69,7 @@ class EventsProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. */ - void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits = 0); + virtual void requestData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits = 0); /**Looks up for the first of the events included in the list @p selectedIds existing after the time @p startTime. * All the events included in the time interval given by @p timeFrame are retrieved. The time interval start time is @@ -79,7 +79,7 @@ class EventsProvider : public DataProvider { * @param selectedIds list of event ids to look up for. * @param initiator instance requesting the data. */ - void requestNextEventData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator); + virtual void requestNextEventData(long startTime,long timeFrame,const QList &selectedIds,QObject* initiator); /**Looks up for the first of the events included in the list @p selectedIds existing before the time @p endTime. @@ -90,12 +90,12 @@ class EventsProvider : public DataProvider { * @param selectedIds list of event ids to look up for. * @param initiator instance requesting the data. */ - void requestPreviousEventData(long endTime,long timeFrame,QList selectedIds,QObject* initiator); + virtual void requestPreviousEventData(long endTime,long timeFrame,QList selectedIds,QObject* initiator); /**Loads the event ids and the corresponding spike time. * @return an loadReturnMessage enum giving the load status */ - int loadData(); + virtual int loadData(); /**Returns the number of events in the event file the provider provides the data for * @return number of events. @@ -167,6 +167,9 @@ class EventsProvider : public DataProvider { /**Initializes the provider as it is the provider of a new empty event file.*/ void initializeEmptyProvider(); + /** Updates mapping saved in eventIds and idsDescriptions as well as descriptionLength based on loaded data. */ + void updateMappingAndDescriptionLength(); + /**Updates the sampling rate for the current document. * @param rate sampling rate. */ @@ -232,7 +235,7 @@ class EventsProvider : public DataProvider { */ void eventDescriptionRemoved(QString providerName,QMap oldNewEventIds,QMap newOldEventIds,int eventIdToRemove,QString eventDescriptionToRemove); -private: +protected: /**Provider's name.*/ QString name; diff --git a/src/globaleventsprovider.h b/src/globaleventsprovider.h index d3c14ed..fb72266 100644 --- a/src/globaleventsprovider.h +++ b/src/globaleventsprovider.h @@ -26,7 +26,7 @@ #include #include //include files for the application -#include +#include "dataprovider.h" #include #include "eventdata.h" diff --git a/src/hdf5utilities.cpp b/src/hdf5utilities.cpp new file mode 100644 index 0000000..812e38f --- /dev/null +++ b/src/hdf5utilities.cpp @@ -0,0 +1,251 @@ +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +// The HDF5Utilities class provides a few convenient functions to read data from an HDF5 file. +#include "hdf5utilities.h" + + +/// +/// \brief HDF5Utilities::bTypesMatch returns true if the calling pType matches the dataset type_class. +/// For example, if the user wants a NATIVE_INT, but the dataset holds H5T_FLOAT, the function returns false. +/// \param pType: the desired type to read. +/// \param type_class: the type found in the dataset. +/// \return true if the types match and false if they do not. +/// +bool HDF5Utilities::bTypesMatch(PredType pType, H5T_class_t type_class) +{ + if (pType == PredType::NATIVE_INT) + { + return (type_class == H5T_INTEGER); + } + else if (pType == PredType::NATIVE_FLOAT) + { + return (type_class == H5T_FLOAT); + } + else if (pType == PredType::NATIVE_DOUBLE) + { + return (type_class == H5T_FLOAT); // ToDo: This set should be checked. + } + else if (pType == PredType::STD_REF_OBJ) + { + return (type_class == H5T_REFERENCE); + } + else { + return false; // Programmer has not yet mapped this case. Feel free to add more. + } +} + + + + +/// +/// \brief HDF5Utilities::Read1DArrayStr read an array of string (character) data from references. +/// \param data_out: the string array that is allocated, filled, and returned. +/// \param lLength: length of the string array. +/// \param hsFileName: name of the HDF5 NWB file +/// \param DSN: location within the file to read the string array +/// \return 0 on success and non-zero on error +/// We may not be able to use the template Read1DArray function, since here we jump locations for each string. +int HDF5Utilities::Read1DArrayStr(std::string **data_out, long &lLength, std::string hsFileName, std::string DSN) +{ + H5File file = H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + DataSet dataset = file.openDataSet(DSN.c_str()); + DataSpace dataspace = dataset.getSpace(); + + // Get the dimension size of each dimension in the dataspace + hsize_t dims_out[2]; + int ndims = dataspace.getSimpleExtentDims(dims_out, nullptr); + if (ndims < 1) + { + cout << "number of dimension is too low in Read1DArrayStr()" << endl; + lLength = 0; + return 2; // wrong number of dimensions + } + lLength = static_cast(dims_out[0]); + + *data_out = new std::string[static_cast(lLength)]; + + // Get class of datatype that should be H5T_REFERENCE + H5T_class_t type_class = dataset.getTypeClass(); + //cout << type_class << endl; + if (type_class == H5T_REFERENCE) + { + hobj_ref_t* rbuf = new hobj_ref_t[static_cast(lLength)]; + + try { + // Read the reference buffer selection from disk + dataset.read(rbuf, PredType::STD_REF_OBJ); + } + catch (...) { + cout << "exception in Read1DArrayStr"; + return 3; + } + + // Now read the individual string from the reference locations + char sz180[180]; + for (int ii=0; ii< lLength; ++ii) + { + H5Rget_name(file.getLocId(), H5R_OBJECT, &rbuf[ii], sz180, 180); + (*data_out)[ii] = sz180; + //cout << sz180 << endl; + } + return 0; + } + else { + lLength = 0; + return 1; // wrong type in file + } + +} + +/// +/// \brief HDF5Utilities::GetDataSetTypeNResolution :Get size of the data element stored in file and use it to guess the resolution. +/// \param resolution +/// \param type_class +/// \param dataset +/// \return +/// +int HDF5Utilities::GetDataSetTypeNResolution(int &resolution, H5T_class_t &type_class, DataSet *dataset) +{ + // Get size of the data element stored in file and use it to guess the resolution. + type_class = dataset->getTypeClass(); + //cout << type_class << endl; + if (type_class == H5T_INTEGER) + { + IntType intype = dataset->getIntType(); + size_t size = intype.getSize(); + resolution = static_cast(8 * size); + } + else if (type_class == H5T_FLOAT) + { + FloatType floattype = dataset->getFloatType(); + size_t size = floattype.getSize(); + resolution = static_cast(8 * size); + } + else if (type_class == H5T_REFERENCE) + { + // Not sure if we would use the size of a reference type, so zero it. + resolution = 0; + } + else { + cout << "programmer needs to handle more types in GetDataSetSizes()" << endl; + resolution = 0; + } + + return 0; +} + +/// +/// \brief HDF5Utilities::GetDataSet2DSizes: Get the # rows and columns of a 2D data set. +/// \param lDim0 +/// \param lDim1 +/// \param dataset +/// \return +/// +int HDF5Utilities::GetDataSet2DSizes(long &lDim0, long &lDim1, DataSet *dataset) +{ + DataSpace dataspace = dataset->getSpace(); + + // Get the dimension size of each dimension in the dataspace + hsize_t dims_out[2]; + int ndims = dataspace.getSimpleExtentDims(dims_out, nullptr); + if (ndims < 2) + { + cout << "number of dimension is too low in GetDataSetSizes()" << endl; + lDim0 = lDim1 = 0; + return 1; + } + lDim0 = static_cast(dims_out[0]); // length + lDim1 = static_cast(dims_out[1]); // nbChannels + + return 0; +} + +/// +/// \brief HDF5Utilities::GetDataSetSizes: read the size and resolution of the 2D data set. +/// \param lDim0 +/// \param lDim1 +/// \param resolution +/// \param type_class +/// \param dataset +/// \return 0 on success and non-zero on failure +/// +int HDF5Utilities::GetDataSetSizes(long &lDim0, long &lDim1, int &resolution, H5T_class_t &type_class, DataSet *dataset) +{ + // Get size of the data element stored in file and use it to guess the resolution. + GetDataSetTypeNResolution(resolution, type_class, dataset); + + // Get the dimension size of each dimension in the dataspace + GetDataSet2DSizes(lDim0, lDim1, dataset); + + return 0; +} + + +/// +/// \brief HDF5Utilities::GetDataSetSizes: read the size and resolution of the 2D data set within a file at a location +/// \param lDim0 +/// \param lDim1 +/// \param resolution +/// \param type_class +/// \param file: the name of the file to read +/// \param DSN: the location within the file to read a dataset. +/// \return zero on success and non-zero otherwise. +/// +int HDF5Utilities::GetDataSetSizes(long &lDim0, long &lDim1, int &resolution, H5T_class_t &type_class, H5::H5File* file, std::string DSN) +{ + DataSet *dataset = new DataSet(file->openDataSet(DSN.c_str())); + int iRet = GetDataSetSizes(lDim0, lDim1, resolution, type_class, dataset); + delete dataset; + return iRet; +} + + +/// +/// \brief HDF5Utilities::GetAttributeDouble Helper function to read in a double precision number from a attribute label. +/// \param dAttr : the double precision answer +/// \param file: the name of the file to read +/// \param DSN: the location within the file to for the dataset +/// \param attrName: the attribite or label to read +/// \return 0 on success. +/// +int HDF5Utilities::GetAttributeDouble(double &dAttr, H5::H5File* file, std::string DSN, std::string attrName) +{ + DataSet *startSet = new DataSet(file->openDataSet(DSN.c_str())); + Attribute attr = startSet->openAttribute(attrName); + DataType dtype = attr.getDataType(); + //std::cout << "attribute type " << dtype << std::endl; + attr.read(dtype, &dAttr); + delete startSet; + + return 0; +} + +/// +/// \brief HDF5Utilities::GetAttributeType Helper function to read an attribute type from an attribute label. +/// \param dtype : the attribute type answer +/// \param file: the name of the file to read +/// \param DSN: the location within the file to for the dataset +/// \param attrName: the attribite or label to read +/// \return 0 on success. +/// +int HDF5Utilities::GetAttributeType(DataType &dtype, H5::H5File* file, std::string DSN, std::string attrName) +{ + DataSet *startSet = new DataSet(file->openDataSet(DSN.c_str())); + Attribute attr = startSet->openAttribute(attrName); + dtype = attr.getDataType(); + delete startSet; + + return 0; +} + + + + + diff --git a/src/hdf5utilities.h b/src/hdf5utilities.h new file mode 100644 index 0000000..156ace4 --- /dev/null +++ b/src/hdf5utilities.h @@ -0,0 +1,200 @@ +#ifndef HDF5UTILITIES_H +#define HDF5UTILITIES_H + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifdef OLD_HEADER_FILENAME +#include +#else +#include +#endif +using std::cout; +using std::endl; +#include +#include "H5Cpp.h" +#include "hdf5.h" +#include "H5File.h" +using namespace H5; + + +/// +/// \brief The HDF5Utilities class provides a few convenient functions to read data from an HDF5 file. +/// +class HDF5Utilities +{ +public: + + bool bTypesMatch(PredType pType, H5T_class_t type_class); + + int Read1DArrayStr(std::string **data_out, long &lLength, std::string hsFileName, std::string DSN); + + int GetDataSet2DSizes(long &lDim0, long &lDim1, DataSet *dataset); + int GetDataSetTypeNResolution(int &resolution, H5T_class_t &type_class, DataSet *dataset); + int GetDataSetSizes(long &lDim0, long &lDim1, int &resolution, H5T_class_t &type_class, DataSet *dataset); + int GetDataSetSizes(long &lDim0, long &lDim1, int &resolution, H5T_class_t &type_class, H5::H5File* file, std::string DSN); + + int GetAttributeDouble(double &dAttr, H5::H5File* file, std::string DSN, std::string attrName); + int GetAttributeType(DataType &dtype, H5::H5File* file, std::string DSN, std::string attrName); + + // General HDF5 function to read a multi-dimensional hyperslab + // data_out holds the read data. + // pType is the data type, such as some form of integer or floating point. + // startOffsets is the beginning point of the data. + // count is the dimensions of the data to be read. + // hsFileName is the name of the HDF5 file + // DSN is the location of the dataset. + template + int ReadHyperSlab(T *data_out, PredType pType, hsize_t *startOffsets, hsize_t *count, std::string hsFileName, std::string DSN ) + { + try + { + //std::cout << "ReadHyperSlab " << std::endl; + + //Turn off the auto-printing when failure occurs so that we can handle the errors appropriately + // RHM believes that exceptions were caught within the HDF5 libraries, so this setting did not help much. + H5::Exception::dontPrint(); + + + // The stride array allows you to sample elements along a dimension.For example, a stride of one(or NULL) will select every element + // along a dimension, a stride of two will select every other element, and a stride of three will select an element after every + // two elements. + //The block array determines the size of the element block selected from a dataspace.If the block size is one or NULL then the block + // size is a single element in that dimension. + hsize_t stride[2]{ 1,1 }; // Stride of hyperslab + hsize_t block[2]{ 1,1 }; // Block sizes + + // Open the file and dataset and get the dataspace. + H5File* file = new H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + DataSet* dataset = new DataSet(file->openDataSet(DSN.c_str())); + + DataSpace fspace = dataset->getSpace(); + fspace.selectHyperslab(H5S_SELECT_SET, count, startOffsets, stride, block); + + // Create memory dataspace. + //Select hyperslab in memory. Hyperslab has the same size and shape as the selected hyperslab for the file dataspace. + DataSpace mspace(2, count); + hsize_t startMemOffsets[2]{0,0}; // Start offsets of hyperslab row, column + mspace.selectHyperslab(H5S_SELECT_SET, count, startMemOffsets, stride, block); + + // Read data back to the buffer matrix. + dataset->read(data_out, pType, mspace, fspace); + + // Close the dataset and the file. + delete dataset; + delete file; + //std::cout << "Done ReadHyperSlab " << std::endl; + + } // end of try block + // catch failure caused by the H5File operations + catch (FileIException error) + { + std::cout << "FileIException " << std::endl; + //error.printError(); + return -1; + } + // catch failure caused by the DataSet operations + catch (DataSetIException error) + { + std::cout << "DataSetIException " << std::endl; + //error.printError(); + return -1; + } + // catch failure caused by the DataSpace operations + catch (DataSpaceIException error) + { + std::cout << "DataSpaceIException " << std::endl; + //error.printError(); + return -1; + } + // catch failure caused by the DataSpace operations + catch (DataTypeIException error) + { + std::cout << "DataTypeIException " << std::endl; + //error.printError(); + return -1; + } + catch (...) + { + std::cout << " RHS exception error RHS " << std::endl; + //error.printError(); + return -1; + } + return 0; + } + + // Function to read a 2D block of data from a HDF5 file at a named location. + // 1D if nChannels = 1. + // There is a slight convenience with using this function. + // For trace voltages, for example, we may want to read in all the channels, but look at 1250 points at one time. + // Thus, we use a starting offset and a length of 1250, but the channels will be zero to nChannels-1. + // + // data_out holds the data that was read from the file. + // pType is the type of data we want, say some form of integer. + // iStart is an index of when to start grabbing data. + // nLength tells us how many data points to read, say 1250. + // nChannels is the number of channels we collected, say 64. + // hsFileName is the name of the HDF5 file. + // DSN is the location within the file that holds teh dataset. + template + void Read2DBlockDataNamed(T *data_out, PredType pType, int iStart, long nLength, int nChannels, std::string hsFileName, std::string DSN) + { + hsize_t startOffsets[2]{ static_cast (iStart), 0 }; // Start offsets of hyperslab row, column + hsize_t count[2]{ static_cast (nLength), static_cast(nChannels) }; // Block count + ReadHyperSlab(data_out, pType, startOffsets, count, hsFileName, DSN); + } + + // Helper function to read in a 1D array. + // The array memory (data_out) is allocated and the lLength is filled in with the length of the data. + // pType is the type of data that we expect from the file, say some type of integer. + // hsFileName is the name of the HDF5 file. + // DSN is the location within the file where the dataset resides. + template + int Read1DArray(T **data_out, PredType pType, long &lLength, std::string hsFileName, std::string DSN) + { + H5File file = H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + DataSet dataset = file.openDataSet(DSN.c_str()); + + // Make sure we have the correct data type + H5T_class_t type_class = dataset.getTypeClass(); + if (!bTypesMatch( pType, type_class)) + { + std::cout << "type classes do not match in Read1DArray" << type_class << std::endl; + lLength=0; + return 1; + } + + DataSpace dataspace = dataset.getSpace(); + + // Get the dimension size of each dimension in the dataspace + hsize_t dims_out[2]; + int ndims = dataspace.getSimpleExtentDims(dims_out, nullptr ); + if (ndims < 1) + { + lLength=0; + return 1; + } + lLength = static_cast(dims_out[0]); + + *data_out = new T[lLength]; + Read2DBlockDataNamed(*data_out, pType, 0, lLength, 1, hsFileName, DSN); + + // Print the 1D array for debugging + //for (int i=0; i<10 /*lLength*/; ++i) + //{ + // std::cout << i << " data: " << (*data_out)[i] << std::endl; + //} + + return 0; + } + +}; + + +#endif // HDF5UTILITIES_H diff --git a/src/icons/esd.png b/src/icons/esd.png new file mode 100644 index 0000000..562d48f Binary files /dev/null and b/src/icons/esd.png differ diff --git a/src/icons/media-minus.png b/src/icons/media-minus.png new file mode 100644 index 0000000..7144805 Binary files /dev/null and b/src/icons/media-minus.png differ diff --git a/src/icons/media-plus.png b/src/icons/media-plus.png new file mode 100644 index 0000000..dca1ce0 Binary files /dev/null and b/src/icons/media-plus.png differ diff --git a/src/icons/media-seek-forward.png b/src/icons/media-seek-forward.png new file mode 100644 index 0000000..0a33f97 Binary files /dev/null and b/src/icons/media-seek-forward.png differ diff --git a/src/itemgroupview.cpp b/src/itemgroupview.cpp index 9e2b479..3896e1d 100644 --- a/src/itemgroupview.cpp +++ b/src/itemgroupview.cpp @@ -53,7 +53,7 @@ ItemGroupView::ItemGroupView(const QColor& backgroundColor,QWidget* parent) ItemGroupView::~ItemGroupView() { - qDebug()<<"in ~ItemGroupView()"; + //qDebug()<<"in ~ItemGroupView()"; } void ItemGroupView::setIconView(ItemIconView *view){ diff --git a/src/itempalette.cpp b/src/itempalette.cpp index 5a2f018..d0c13d5 100644 --- a/src/itempalette.cpp +++ b/src/itempalette.cpp @@ -852,11 +852,13 @@ void ItemPalette::removeGroup(const QString &groupName){ //a group must always be selected. if(selected == groupName){ if(type == CLUSTER && !clusterGroupList.isEmpty()){ - qSort(clusterGroupList); + //qSort(clusterGroupList); + std::sort(clusterGroupList.begin(), clusterGroupList.end()); selectGroupLabel(QString::number(clusterGroupList.at(0))); } else if(type == EVENT && !itemGroupList.isEmpty()){ - qSort(itemGroupList); + //qSort(itemGroupList); + std::sort(itemGroupList.begin(), itemGroupList.end()); selectGroupLabel(itemGroupList.at(0)); } else selected.clear();//never reach @@ -866,12 +868,14 @@ void ItemPalette::removeGroup(const QString &groupName){ void ItemPalette::selectGroup(const QString& groupName){ if(type == CLUSTER && !clusterGroupList.isEmpty()){ - qSort(clusterGroupList); + //qSort(clusterGroupList); + std::sort(clusterGroupList.begin(), clusterGroupList.end()); if(clusterGroupList.contains(groupName.toInt())) selectGroupLabel(groupName); else selectGroupLabel(QString::number(clusterGroupList.at(0))); } else if(type == EVENT && !itemGroupList.isEmpty()){ - qSort(itemGroupList); + //qSort(itemGroupList); + std::sort(itemGroupList.begin(), itemGroupList.end()); if(itemGroupList.contains(groupName)) selectGroupLabel(groupName); else selectGroupLabel(itemGroupList.at(0)); } @@ -954,12 +958,14 @@ void ItemPalette::orderTheGroups(){ } if(type == CLUSTER) { - qSort(clusterGroupList); + //qSort(clusterGroupList); + std::sort(clusterGroupList.begin(), clusterGroupList.end()); QList::iterator iterator; for(iterator = clusterGroupList.begin(); iterator != clusterGroupList.end(); ++iterator) verticalContainer->addWidget(itemGroupViewDict[QString::number(*iterator)]); } else { - qSort(itemGroupList); + //qSort(itemGroupList); + std::sort(itemGroupList.begin(), itemGroupList.end()); QStringList::iterator iterator; for(iterator = itemGroupList.begin(); iterator != itemGroupList.end(); ++iterator) verticalContainer->addWidget(itemGroupViewDict[*iterator]); diff --git a/src/main.cpp b/src/main.cpp index 51b4610..d30cd2b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,14 +15,16 @@ * * ***************************************************************************/ - #include "config-neuroscope.h" +// include C++ include file +#include + // include files for QT #include #include #include -#include + //Application specific include files #include "neuroscope.h" int main(int argc, char *argv[]) @@ -31,8 +33,8 @@ int main(int argc, char *argv[]) #if QT_VERSION < 0x050000 QApplication::setGraphicsSystem("raster"); #endif - QApplication::setOrganizationName("sourceforge"); - QApplication::setOrganizationDomain("sourceforge.net"); + QApplication::setOrganizationName("neurosuite"); + QApplication::setOrganizationDomain("neurosuite.github.io"); QApplication::setApplicationName("neuroscope"); QApplication app(argc, argv); @@ -46,14 +48,14 @@ int main(int argc, char *argv[]) QString amplification; QString screenGain; QString timeWindow; + bool streamMode = false; //TODO Qt5.2 use QCommandLineParser for (int i = 1, n = args.size(); i < n; ++i) { const QString arg = args.at(i); if (arg == "-h" || arg == "--help" || arg == "-help") { - qWarning() << "Usage: " << qPrintable(args.at(0)) - << " [file]" - << "\n\n" - << "Arguments:\n" + std::cerr << "Usage: " << qPrintable(args.at(0)) << " [file]\n" + << "\n" + << "Optional settings:\n" << " -r, --resolution Resolution of the acquisition system.\n" << " -c, --nbChannels Number of channels.\n" << " -o, --offset Initial offset.\n" @@ -62,60 +64,91 @@ int main(int argc, char *argv[]) << " -g, --screenGain Screen gain.\n" << " -s, --samplingRate Sampling rate.\n" << " -t, --timeWindow Initial time window (in miliseconds).\n" - << " -h, --help print this help\n"; + << "\n" + << "Optional flags:\n" + #if WITH_CEREBUS + << " -n, --stream Open network stream instead of file.\n" + #endif + << " -h, --help print this help\n" + << " -v, --version print version info\n"; return 1; + } else if (arg == "-v" || arg == "--version" || arg == "-version") { + std::cout << "NeuroScope " << NEUROSCOPE_VERSION << std::endl; + return 0; } bool handled = true; - if (i < n - 1) { - if (arg == "-r" || arg == "--resolution" || arg == "-resolution") + if (i < n - 1) { // Parameter value flags + if (arg == "-r" || arg == "--resolution" || arg == "-resolution") { resolution = args.at(++i); - else if (arg == "-c" || arg == "--nbChannels" || arg == "-nbChannels") { + } else if (arg == "-c" || arg == "--nbChannels" || arg == "-nbChannels") { channelNb = args.at(++i); - } - else if (arg == "o-" || arg == "--offset" || arg == "-offset") + } else if (arg == "-o" || arg == "--offset" || arg == "-offset") { offset = args.at(++i); - else if (arg == "-m" || arg == "--voltageRange" || arg == "-voltageRange") + } else if (arg == "-m" || arg == "--voltageRange" || arg == "-voltageRange") { voltageRange = args.at(++i); - else if (arg == "-a" || arg == "--amplification" || arg == "-amplification") + } else if (arg == "-a" || arg == "--amplification" || arg == "-amplification") { amplification = args.at(++i); - else if (arg == "-g" || arg == "--screenGain" || arg == "-screenGain") + } else if (arg == "-g" || arg == "--screenGain" || arg == "-screenGain") { screenGain = args.at(++i); - else if (arg == "-s" || arg == "--samplingRate" || arg == "-samplingRate") + } else if (arg == "-s" || arg == "--samplingRate" || arg == "-samplingRate") { SR = args.at(++i); - else if (arg == "-t" || arg == "--timeWindow" || arg == "-timeWindow") + } else if (arg == "-t" || arg == "--timeWindow" || arg == "-timeWindow") { timeWindow = args.at(++i); - else +#ifdef WITH_CEREBUS + } else if (arg == "-n" || arg == "--stream" || arg == "-stream") { + streamMode = true; +#endif + } else { handled = false; - + } } else { - handled = false; + handled = false; } // Nothing know. Treat it as path. - if (!handled || (n == 2) ) + if (!handled) file = args.at(i); } + + if (file.startsWith(QLatin1String("-")) ) { + std::cerr << "The flag '" << file.toStdString() << "' is unknown or missing a parameter." << std::endl; + return 1; + } + NeuroscopeApp* neuroscope = new NeuroscopeApp(); neuroscope->setFileProperties(channelNb,SR,resolution, offset,voltageRange,amplification, screenGain,timeWindow); neuroscope->show(); - if (!file.isEmpty()) { - QFileInfo fInfo(file); - if (file.startsWith(QLatin1String("-")) ) { - qWarning() << "it's not a filename :"<openDocumentFile(url); +#ifdef WITH_CEREBUS + if (streamMode) { + if(!file.isEmpty()) { + int group = file.toInt(); + if(group > 0 && group < 6) { + neuroscope->openNetworkStream(static_cast(group)); + } else { + std::cerr << "Sampling group must be between 1 (500 samp/sec) and 5 (30k samp/sec)." << std::endl; + } } else { - neuroscope->openDocumentFile(file); - } + std::cerr << "Network stream mode expects a sampling group as file argument." << std::endl; + } + } else { +#endif + if (!file.isEmpty()) { + QFileInfo fInfo(file); + if(fInfo.isRelative()) { + const QString url = QDir::currentPath().append("/") + file; + neuroscope->openDocumentFile(url); + } else { + neuroscope->openDocumentFile(file); + } + } +#ifdef WITH_CEREBUS } - +#endif const int ret = app.exec(); delete neuroscope; return ret; -} - +} diff --git a/src/neuroscope-icons.qrc b/src/neuroscope-icons.qrc index 2fb27f1..b9e550b 100644 --- a/src/neuroscope-icons.qrc +++ b/src/neuroscope-icons.qrc @@ -4,6 +4,7 @@ icons/anatomy.png icons/backCluster.png icons/backEvent.png + icons/esd.png icons/clusters.png icons/new_group.png icons/remove.png @@ -25,5 +26,8 @@ icons/trash.png icons/zoom_tool.png icons/measure_tool.png + icons/media-seek-forward.png + icons/media-plus.png + icons/media-minus.png diff --git a/src/neuroscope.cpp b/src/neuroscope.cpp index c83a003..ae2aeb9 100644 --- a/src/neuroscope.cpp +++ b/src/neuroscope.cpp @@ -14,7 +14,6 @@ * (at your option) any later version. * * * ***************************************************************************/ - #include "config-neuroscope.h" // include files for QT @@ -55,22 +54,22 @@ #include "itempalette.h" #include "eventsprovider.h" #include "qhelpviewer.h" - +#include "nwblocations.h" NeuroscopeApp::NeuroscopeApp() - :QMainWindow(0) - ,prefDialog(0L) + :QMainWindow(nullptr /*0*/) + ,prefDialog(nullptr /*0L*/) ,displayCount(0) - ,mainDock(0) - ,spikeChannelPalette(0) - ,tabsParent(0L) - ,paletteTabsParent(0L), + ,mainDock(nullptr /*0*/) + ,spikeChannelPalette(nullptr /*0*/) + ,tabsParent(nullptr /*0L*/) + ,paletteTabsParent(nullptr /*0L*/), isInit(true) ,groupsModified(false) ,colorModified(false) ,eventsModified(false) ,initialOffsetDefault(0) - ,propertiesDialog(0L) + ,propertiesDialog(nullptr /*0L*/) ,select(false) ,initialTimeWindow(0) ,undoRedoInprocess(false) @@ -131,6 +130,12 @@ void NeuroscopeApp::initActions() mOpenAction->setIcon(QPixmap(":/shared-icons/document-open")); connect(mOpenAction, SIGNAL(triggered()), this, SLOT(slotFileOpen())); +#ifdef WITH_CEREBUS + mStreamAction = fileMenu->addAction(tr("Open network stream...")); + mStreamAction->setIcon(QPixmap(":/icons/esd")); + connect(mStreamAction, SIGNAL(triggered()), this, SLOT(slotStreamOpen())); +#endif + mFileOpenRecent = new QRecentFileAction(this); QSettings settings; mFileOpenRecent->setRecentFiles(settings.value(QLatin1String("Recent Files"),QStringList()).toStringList()); @@ -480,15 +485,18 @@ void NeuroscopeApp::initActions() traceMenu->addSeparator(); mPage = traceMenu->addAction(tr("Auto-advance to end of recording")); mPage->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Space); + mPage->setIcon(QPixmap(":/icons/media-seek-forward")); mPage->setCheckable(true); connect(mPage,SIGNAL(triggered()), this,SLOT(page())); - mAccelerate = traceMenu->addAction(tr("Accelerate")); + mAccelerate = traceMenu->addAction(tr("Accelerate")); mAccelerate->setShortcut(Qt::CTRL + Qt::Key_Up); + mAccelerate->setIcon(QPixmap(":/icons/media-plus")); connect(mAccelerate,SIGNAL(triggered()), this,SLOT(accelerate())); mDecelerate = traceMenu->addAction(tr("Decelerate")); mDecelerate->setShortcut(Qt::CTRL + Qt::Key_Down); + mDecelerate->setIcon(QPixmap(":/icons/media-minus")); connect(mDecelerate,SIGNAL(triggered()), this,SLOT(decelerate())); @@ -532,18 +540,20 @@ void NeuroscopeApp::initActions() settingsMenu->addSeparator(); + calibrationBar = settingsMenu->addAction(tr("&Display Calibration")); calibrationBar->setCheckable(true); connect(calibrationBar,SIGNAL(triggered()), this,SLOT(slotShowCalibration())); calibrationBar->setChecked(false); - settingsMenu->addSeparator(); - mPreferenceAction = settingsMenu->addAction(tr("Preferences")); + // RHM thinks that the menu name "Preferences" was not allowed on the Mac. Windows was fine. + mPreferenceAction = settingsMenu->addAction(tr("...Preferences")); mPreferenceAction->setIcon(QIcon(":/shared-icons/configure")); connect(mPreferenceAction,SIGNAL(triggered()), this,SLOT(executePreferencesDlg())); + //Help menu QMenu *helpMenu = menuBar()->addMenu(tr("&Help")); QAction *handbook = helpMenu->addAction(tr("Handbook")); @@ -559,6 +569,12 @@ void NeuroscopeApp::initActions() connect(doc, SIGNAL(loadFirstDisplay(QList*,bool,bool,bool,bool,bool,bool,bool,QList,QList,QList,QMap&,long,long,QString,bool,int,bool)),this, SLOT(slotSetUp(QList*,bool,bool,bool,bool,bool,bool,bool,QList,QList,QList,QMap&,long,long,QString,bool,int,bool))); + connect(doc, SIGNAL(clusterFileLoaded(const QString&)), + this, SLOT(slotClusterFileLoaded(const QString&))); + + connect(doc, SIGNAL(eventFileLoaded(const QString&)), + this, SLOT(slotEventFileLoaded(const QString&))); + connect(displayChannelPalette, SIGNAL(singleChangeColor(int)),this, SLOT(slotSingleChannelColorUpdate(int))); connect(spikeChannelPalette, SIGNAL(singleChangeColor(int)),this, SLOT(slotSingleChannelColorUpdate(int))); connect(displayChannelPalette, SIGNAL(singleChangeColor(int)),spikeChannelPalette, SLOT(updateColor(int))); @@ -584,10 +600,10 @@ void NeuroscopeApp::initActions() connect(spikeChannelPalette, SIGNAL(channelsDiscarded(QList)),this, SLOT(slotChannelsDiscarded(QList))); - connect(displayChannelPalette, SIGNAL(channelsMovedToTrash(QList,QString,bool)),spikeChannelPalette, SLOT(discardChannels(QList,QString,bool))); - connect(spikeChannelPalette, SIGNAL(channelsMovedToTrash(QList,QString,bool)),displayChannelPalette, SLOT(discardChannels(QList,QString,bool))); - connect(displayChannelPalette, SIGNAL(channelsMovedAroundInTrash(QList,QString,bool)),spikeChannelPalette, SLOT(trashChannelsMovedAround(QList,QString,bool))); - connect(spikeChannelPalette, SIGNAL(channelsMovedAroundInTrash(QList,QString,bool)),displayChannelPalette, SLOT(trashChannelsMovedAround(QList,QString,bool))); + connect(displayChannelPalette, SIGNAL(channelsMovedToTrash(QList,int,bool)),spikeChannelPalette, SLOT(discardChannels(QList,int,bool))); + connect(spikeChannelPalette, SIGNAL(channelsMovedToTrash(QList,int,bool)),displayChannelPalette, SLOT(discardChannels(QList,int,bool))); + connect(displayChannelPalette, SIGNAL(channelsMovedAroundInTrash(QList,int,bool)),spikeChannelPalette, SLOT(trashChannelsMovedAround(QList,int,bool))); + connect(spikeChannelPalette, SIGNAL(channelsMovedAroundInTrash(QList,int,bool)),displayChannelPalette, SLOT(trashChannelsMovedAround(QList,int,bool))); connect(displayChannelPalette, SIGNAL(channelsRemovedFromTrash(QList)),spikeChannelPalette, SLOT(removeChannelsFromTrash(QList))); connect(spikeChannelPalette, SIGNAL(channelsRemovedFromTrash(QList)),displayChannelPalette, SLOT(removeChannelsFromTrash(QList))); @@ -603,12 +619,19 @@ void NeuroscopeApp::initActions() mMainToolBar = new QToolBar; mMainToolBar->setObjectName("maintoolbar"); mMainToolBar->addAction(mOpenAction); +#ifdef WITH_CEREBUS + mMainToolBar->addAction(mStreamAction); +#endif mMainToolBar->addAction(mSaveAction); mMainToolBar->addAction(mPrintAction); mMainToolBar->addSeparator(); mMainToolBar->addAction(mUndo); mMainToolBar->addAction(mRedo); mMainToolBar->addSeparator(); + mMainToolBar->addAction(mDecelerate); + mMainToolBar->addAction(mPage); + mMainToolBar->addAction(mAccelerate); + mMainToolBar->addSeparator(); mMainToolBar->addAction(editMode); addToolBar(mMainToolBar); @@ -676,7 +699,7 @@ void NeuroscopeApp::initItemPanel(){ paletteTabsParent->hide(); } void NeuroscopeApp::executePreferencesDlg(){ - if(prefDialog == 0L){ + if(prefDialog == nullptr /*0L*/){ prefDialog = new PrefDialog(this); // connect to the "settingsChanged" signal connect(prefDialog,SIGNAL(settingsChanged()),this,SLOT(applyPreferences())); @@ -877,7 +900,7 @@ void NeuroscopeApp::initDisplay(QList* channelsToDisplay,bool autocenterCha NeuroscopeView* view = new NeuroscopeView(*this,tabLabel,startTime,duration,backgroundColor,Qt::WA_DeleteOnClose,statusBar(),channelsToDisplay,greyScale->isChecked(), doc->tracesDataProvider(),displayMode->isChecked(),clusterVerticalLines->isChecked(), - clusterRaster->isChecked(),clusterWaveforms->isChecked(),showHideLabels->isChecked(),doc->getGain(),doc->getAcquisitionGain(), + clusterRaster->isChecked(),clusterWaveforms->isChecked(),showHideLabels->isChecked(),doc->getScreenGain(), doc->channelColors(),doc->getDisplayGroupsChannels(),doc->getDisplayChannelsGroups(),autocenterChannels, offsets,channelGains,selectedChannels,skipStatus,rasterHeight,doc->getTraceBackgroundImage(),mainDock,"TracesDisplay"); @@ -888,9 +911,12 @@ void NeuroscopeApp::initDisplay(QList* channelsToDisplay,bool autocenterCha connect(view,SIGNAL(eventRemoved(QString,int,double)),this, SLOT(slotEventRemoved(QString,int,double))); connect(view,SIGNAL(eventAdded(QString,QString,double)),this, SLOT(slotEventAdded(QString,QString,double))); connect(view,SIGNAL(positionViewClosed()),this, SLOT(positionViewClosed())); - - /// Added by M.Zugaro to enable automatic forward paging - connect(view,SIGNAL(stopped()),this,SLOT(neuroscopeViewStopped())); + + // Listen to changes in automatic forward paging + connect(view, SIGNAL(pagingStarted()), + this, SLOT(neuroscopeViewStarted())); + connect(view, SIGNAL(pagingStopped()), + this, SLOT(neuroscopeViewStopped())); //Keep track of the number of displays displayCount ++; @@ -905,9 +931,9 @@ void NeuroscopeApp::initDisplay(QList* channelsToDisplay,bool autocenterCha //Initialize and dock the displayPanel //Create the channel lists and select the channels which will be drawn - displayChannelPalette->createChannelLists(doc->channelColors(),doc->getDisplayGroupsChannels(),doc->getDisplayChannelsGroups()); + displayChannelPalette->createChannelLists(doc->channelColors(),doc->getDisplayGroupsChannels(),doc->getDisplayChannelsGroups(),doc->getChannelLabels()); displayChannelPalette->updateShowHideStatus(*channelsToDisplay,true); - spikeChannelPalette->createChannelLists(doc->channelColors(),doc->getSpikeGroupsChannels(),doc->getChannelsSpikeGroups()); + spikeChannelPalette->createChannelLists(doc->channelColors(),doc->getSpikeGroupsChannels(),doc->getChannelsSpikeGroups(),doc->getChannelLabels()); spikeChannelPalette->updateShowHideStatus(*channelsToDisplay,true); displayChannelPalette->setGreyScale(greyScale->isChecked()); spikeChannelPalette->setGreyScale(greyScale->isChecked()); @@ -1052,6 +1078,46 @@ void NeuroscopeApp::openDocumentFile(const QString& url) slotStatusMsg(tr("Ready.")); } +#ifdef WITH_CEREBUS +void NeuroscopeApp::openNetworkStream(CerebusTracesProvider::SamplingGroup group) +{ + slotStatusMsg(tr("Opening stream...")); + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + + //If no document is open already, open the document asked. + if(!mainDock) { + // Fix for file name based logic + this->filePath = "cerebus.nsx"; + + // Open stream + if(!doc->openStream(group)) { + QApplication::restoreOverrideCursor(); + doc->closeDocument(); + resetState(); + return; + } + + // Update the spike and event browsing status + updateBrowsingStatus(); + + // Start auto advance + page(); + + setWindowTitle("Network Stream"); + QApplication::restoreOverrideCursor(); + } else { + // ToDo: Check if we are already in streaming mode + if (!QProcess::startDetached("neuroscope", QStringList() + << "-n" + << QString::number(group))) { + QMessageBox::critical(this, tr("Neuroscope"),tr("neuroscope can not be launch")); + } + QApplication::restoreOverrideCursor(); + } + slotStatusMsg(tr("Ready.")); +} +#endif NeuroscopeDoc* NeuroscopeApp::getDocument() const { @@ -1060,7 +1126,7 @@ NeuroscopeDoc* NeuroscopeApp::getDocument() const void NeuroscopeApp::updateBrowsingStatus(){ if(!clusterFileList.isEmpty()){ - ItemPalette* palette = 0; + ItemPalette* palette = nullptr /*0*/; for(int i = 0; icount();++i){ QWidget* current = paletteTabsParent->widget(i); QString name = current->objectName(); @@ -1094,7 +1160,7 @@ void NeuroscopeApp::updateBrowsingStatus(){ } } - if (!palette) + if (!palette) return; NeuroscopeView* view = activeView(); QStringList::iterator iterator; @@ -1246,8 +1312,12 @@ void NeuroscopeApp::slotFileOpen() slotStatusMsg(tr("Opening file...")); QSettings settings; - const QString url=QFileDialog::getOpenFileName(this, tr("Open File..."), settings.value("CurrentDirectory").toString(), - tr("Data File (*.dat *.lfp *.eeg *.fil);;All files (*.*)") ); + const QString url=QFileDialog::getOpenFileName( + this, + tr("Open File..."), + settings.value("CurrentDirectory").toString(), + tr("Data File (*.dat *.lfp *.eeg *.fil *.nwb);;Blackrock File (*.ns1 *.ns2 *.ns3 *.ns4 *.ns5 *.ns6);;All files (*.*)") + ); if(!url.isEmpty()) { QDir CurrentDir; @@ -1258,12 +1328,44 @@ void NeuroscopeApp::slotFileOpen() slotStatusMsg(tr("Ready.")); } +#ifdef WITH_CEREBUS +void NeuroscopeApp::slotStreamOpen() +{ + slotStatusMsg(tr("Opening network stream...")); + + // Let user choose sampling rate + QStringList items; + items << "30 000 Hz" << "10 000 Hz" << "2000 Hz" << "1000 Hz" << "500 Hz"; + + CerebusTracesProvider::SamplingGroup mapping[5] = { + CerebusTracesProvider::RATE_30k, + CerebusTracesProvider::RATE_10k, + CerebusTracesProvider::RATE_2K, + CerebusTracesProvider::RATE_1K, + CerebusTracesProvider::RATE_500 + }; + + bool ok; + int answer = items.indexOf(QInputDialog::getItem(0, tr("Network Stream"), + tr("Please choose the sampling group to be displayed."), + items, + 0, // current + false, //editable + &ok)); + + if (ok && answer >= 0) + openNetworkStream(mapping[answer]); + + slotStatusMsg(tr("Ready.")); +} +#endif + void NeuroscopeApp::slotLoadClusterFiles(){ slotStatusMsg(tr("Loading cluster file(s)...")); QSettings settings; const QStringList urls=QFileDialog::getOpenFileNames(this, tr("Open Cluster Files..."), settings.value("CurrentDirectory").toString(), - tr("Cluster File (*.clu.*)")); + tr("Cluster File (*.clu.*);;Blackrock File (*.nev);;NWB File (*.nwb);;All files (*.*)")); if(!urls.isEmpty()) { QDir CurrentDir; @@ -1279,8 +1381,12 @@ void NeuroscopeApp::slotLoadEventFiles(){ slotStatusMsg(tr("Loading event file(s)...")); QSettings settings; - const QStringList urls=QFileDialog::getOpenFileNames(this, tr("Open Event Files..."), settings.value("CurrentDirectory").toString(), - tr("Event File (*.evt*)")); + const QStringList urls = QFileDialog::getOpenFileNames( + this, + tr("Open Event Files..."), + settings.value("CurrentDirectory").toString(), + tr("Event File (*.evt*);;Blackrock File (*.nev);;NWB Event File (*.nwb);;All files (*.*)") + ); if(!urls.isEmpty()) { QDir CurrentDir; @@ -1342,11 +1448,6 @@ void NeuroscopeApp::slotCreateEventFile(){ QMessageBox::critical (this, tr("Error!"),tr("The selected file name is already opened.")); } else{ - const QString eventFileId = doc->lastLoadedProviderName(); - if(eventFileList.isEmpty()) - createEventPalette(eventFileId); - else - addEventFile(eventFileId); eventsModified = true; QApplication::restoreOverrideCursor(); } @@ -1356,9 +1457,7 @@ void NeuroscopeApp::slotCreateEventFile(){ void NeuroscopeApp::slotFileOpenRecent(const QString& url){ slotStatusMsg(tr("Opening file...")); - openDocumentFile(url); - slotStatusMsg(tr("Ready.")); } @@ -2225,7 +2324,7 @@ void NeuroscopeApp::slotTabChange(int index){ /// Added by M.Zugaro to enable automatic forward paging if ( isStill() ) slotStateChanged("pageOffState"); else slotStateChanged("pageOnState"); - + QWidget *channelPalette = paletteTabsParent->currentWidget(); if(qobject_cast(channelPalette)){ @@ -2532,7 +2631,7 @@ void NeuroscopeApp::createDisplay(QList* channelsToDisplay,bool verticalLin NeuroscopeView* view = new NeuroscopeView(*this,tabLabel,startTime,duration,backgroundColor,Qt::WA_DeleteOnClose,statusBar(),channelsToDisplay, greyMode,doc->tracesDataProvider(),multipleColumns,verticalLines,raster,waveforms,showLabels, - doc->getGain(),doc->getAcquisitionGain(),doc->channelColors(),doc->getDisplayGroupsChannels(),doc->getDisplayChannelsGroups(),autocenterChannels, + doc->getScreenGain(),doc->channelColors(),doc->getDisplayGroupsChannels(),doc->getDisplayChannelsGroups(),autocenterChannels, offsets,channelGains,selectedChannels,displayChannelPalette->getSkipStatus(),rasterHeight,doc->getTraceBackgroundImage(),mainDock, "TracesDisplay"); @@ -2544,8 +2643,12 @@ void NeuroscopeApp::createDisplay(QList* channelsToDisplay,bool verticalLin connect(view,SIGNAL(eventRemoved(QString,int,double)),this, SLOT(slotEventRemoved(QString,int,double))); connect(view,SIGNAL(eventAdded(QString,QString,double)),this, SLOT(slotEventAdded(QString,QString,double))); connect(view,SIGNAL(positionViewClosed()),this, SLOT(positionViewClosed())); - /// Added by M.Zugaro to enable automatic forward paging - connect(view,SIGNAL(stopped()),this,SLOT(neuroscopeViewStopped())); + + // Listen to changes in automatic forward paging + connect(view, SIGNAL(pagingStarted()), + this, SLOT(neuroscopeViewStarted())); + connect(view, SIGNAL(pagingStopped()), + this, SLOT(neuroscopeViewStopped())); view->installEventFilter(this); @@ -2605,7 +2708,7 @@ void NeuroscopeApp::slotSynchronize(){ spikeChannelPalette->reset(); //Update and show the spike Palette. - spikeChannelPalette->createChannelLists(doc->channelColors(),doc->getSpikeGroupsChannels(),doc->getChannelsSpikeGroups()); + spikeChannelPalette->createChannelLists(doc->channelColors(),doc->getSpikeGroupsChannels(),doc->getChannelsSpikeGroups(),doc->getChannelLabels()); spikeChannelPalette->updateShowHideStatus(displayChannelPalette->getShowHideChannels(true),true); spikeChannelPalette->update(); @@ -2751,7 +2854,6 @@ void NeuroscopeApp::loadClusterFiles(const QStringList &urls){ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //Loop on the files - int counter = 0; QStringList::const_iterator iterator; for(iterator = urls.constBegin();iterator != urls.constEnd();++iterator){ //Create the provider @@ -2793,17 +2895,19 @@ void NeuroscopeApp::loadClusterFiles(const QStringList &urls){ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); continue; } - - //Create the cluster palette if need it - counter++; - QString clusterFileId = doc->lastLoadedProviderName(); - if(clusterFileList.isEmpty() && counter == 1) - createClusterPalette(clusterFileId); - else addClusterFile(clusterFileId); } QApplication::restoreOverrideCursor(); } +/** Updates view, because a new cluster file was loaded. */ +void NeuroscopeApp::slotClusterFileLoaded(const QString& fileID) { + // Create the cluster palette if need it + if(clusterFileList.isEmpty()) + createClusterPalette(fileID); + else + addClusterFile(fileID); +} + void NeuroscopeApp::createClusterPalette(const QString& clusterFileId) { ItemPalette* clusterPalette = new ItemPalette(ItemPalette::CLUSTER,backgroundColor,this,"clusterPanel"); @@ -2836,7 +2940,9 @@ void NeuroscopeApp::createClusterPalette(const QString& clusterFileId) slotStateChanged("clusterState"); //Waveforms are allowed only for dat and fil files. - if(filePath.contains(".dat")||filePath.contains(".fil")) { + if(filePath.contains(".dat") + || filePath.contains(".fil") + || filePath.contains(".ns")) { slotStateChanged("datState"); } else{ @@ -2882,7 +2988,7 @@ void NeuroscopeApp::slotUpdateShownClusters(const QMap >& sel QString providerName = groupIterator.key(); QList clusterIds = groupIterator.value(); NeuroscopeView* view = activeView(); - qDebug()<<" void NeuroscopeApp::slotUpdateShownClusters(const QMap >& selection){"< >& selection){"<shownClustersUpdate(providerName,clusterIds); } } @@ -2965,10 +3071,25 @@ void NeuroscopeApp::loadPositionFile(const QString& url){ void NeuroscopeApp::loadEventFiles(const QStringList& urls){ NeuroscopeView* view = activeView(); + + // if NWB, load it differently - multiple events in one file + if(urls[0].indexOf(".nwb") != -1) { + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + NWBLocations *NWB_Locations = new NWBLocations(urls[0].toStdString()); + QList lstDSNs = NWB_Locations->getListGenericTexts("nwb_event_times", ""); + for (int jj=0; jj < lstDSNs.count(); ++jj) + { + //Create the provider + int returnStatus = doc->loadEventFile(urls[0],view, jj); + } + QApplication::restoreOverrideCursor(); + return; + } + // end NWB block + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //Loop on the files - int counter = 0; QStringList::const_iterator iterator; for(iterator = urls.constBegin();iterator != urls.constEnd();++iterator){ //Create the provider @@ -3003,18 +3124,17 @@ void NeuroscopeApp::loadEventFiles(const QStringList& urls){ QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); continue; } - - //Create the event palette if need it - counter++; - QString eventFileId = doc->lastLoadedProviderName(); - if(eventFileList.isEmpty() && counter == 1) - createEventPalette(eventFileId); - else - addEventFile(eventFileId); } QApplication::restoreOverrideCursor(); } +void NeuroscopeApp::slotEventFileLoaded(const QString& fileId) { + //Create the event palette if need it; + if(eventFileList.isEmpty()) + createEventPalette(fileId); + else + addEventFile(fileId); +} void NeuroscopeApp::createEventPalette(const QString& eventFileId){ @@ -3042,8 +3162,8 @@ void NeuroscopeApp::createEventPalette(const QString& eventFileId){ eventPalette->createItemList(doc->providerColorList(eventFileId),eventFileId,doc->getLastEventProviderGridX()); //Disconnect the previous connection - if(paletteTabsParent != NULL) - disconnect(paletteTabsParent,0,0,0); + if(paletteTabsParent != nullptr /*NULL*/) + disconnect(paletteTabsParent,0,nullptr /*0*/,nullptr /*0*/); connect(paletteTabsParent, SIGNAL(currentChanged(int)), this, SLOT(slotPaletteTabChange(int))); @@ -3248,7 +3368,7 @@ void NeuroscopeApp::slotAddEventButtonActivated(QAction *act){ } ItemPalette* NeuroscopeApp::getEventPalette(){ - ItemPalette* eventPalette = 0L; + ItemPalette* eventPalette = nullptr /*0L*/; for(int i = 0; i< paletteTabsParent->count();++i){ QWidget* current = paletteTabsParent->widget(i); @@ -3628,7 +3748,8 @@ void NeuroscopeApp::slotAbout() void NeuroscopeApp::slotHanbook() { QHelpViewer *helpDialog = new QHelpViewer(this); - helpDialog->setHtml(QApplication::applicationDirPath() + NEUROSCOPE_DOC_PATH + QLatin1String("index.html")); + QString qstrFileName = QApplication::applicationDirPath() + NEUROSCOPE_DOC_PATH + QLatin1String("index.html"); + helpDialog->setHtml(qstrFileName); helpDialog->setAttribute( Qt::WA_DeleteOnClose ); helpDialog->show(); } @@ -3690,4 +3811,3 @@ void NeuroscopeApp::slotEventsToBrowse() { slotStateChanged("eventBrowsingState"); } - diff --git a/src/neuroscope.desktop b/src/neuroscope.desktop index 924a71d..2b2f701 100755 --- a/src/neuroscope.desktop +++ b/src/neuroscope.desktop @@ -5,5 +5,5 @@ GenericName=Viewer for neurophysiological and behavioral data Comment= Icon=neuroscope Exec=neuroscope %u -MimeType=application/nphys-dat;application/nphys-fil;application/nphys-lfp;application/nphys-eeg;application/nphys-nrs; +MimeType=application/nphys-dat;application/nphys-fil;application/nphys-lfp;application/nphys-eeg;application/nphys-nrs;application/nphys-ns1;application/nphys-ns2;application/nphys-ns3;application/nphys-ns4;application/nphys-ns4;application/nphys-ns5;application/nphys-ns6;application/nphys-nev; Categories=Qt;Education;Science; diff --git a/src/neuroscope.h b/src/neuroscope.h index 9bbec18..eaec255 100644 --- a/src/neuroscope.h +++ b/src/neuroscope.h @@ -19,7 +19,7 @@ #define NEUROSCOPE_H - + #include @@ -35,6 +35,11 @@ //application specific include files #include "neuroscopeview.h" +#ifdef WITH_CEREBUS + #include "cerebustraceprovider.h" // For SamplingGroup +#endif + + // forward declaration of the Neuroscope classes class NeuroscopeDoc; class PrefDialog; @@ -67,7 +72,14 @@ class NeuroscopeApp : public QMainWindow * Asking for a new one will open a new instance of the application with it. */ void openDocumentFile(const QString& url=QString()); - + +#ifdef WITH_CEREBUS + /** Open a stream, only one document (file or stream) at the time allowed. + * Asking for a new one will open a new instance of the application with it. + */ + void openNetworkStream(CerebusTracesProvider::SamplingGroup group); +#endif + /** Returns a pointer to the current document connected to the NeuroscopeApp instance and is used by * the View class to access the document object's methods */ @@ -187,7 +199,7 @@ class NeuroscopeApp : public QMainWindow * @return true if at least one cluster file is loaded, false otherwise. */ inline bool isClusterFilesLoaded()const{return !clusterFileList.isEmpty();} - + /**Tells if there is a position file loaded. * @return true a position file is loaded, false otherwise. */ @@ -196,18 +208,42 @@ class NeuroscopeApp : public QMainWindow /// Added by M.Zugaro to enable automatic forward paging bool isStill() { return ( !activeView() || activeView()->isStill() ); } -private Q_SLOTS: +public Q_SLOTS: - /// Added by M.Zugaro to enable automatic forward paging - void neuroscopeViewStopped() { slotStateChanged("pageOffState"); } + /** Toggle paging (a.k.a. auto advance to end of recording) */ + void page() { + if(activeView()) { + if(activeView()->isStill()) + activeView()->page(); + else + activeView()->stop(); + } else { + // No view, no paging + slotStateChanged("pageOffState"); + } + } -public Q_SLOTS: + /** Stop paging */ + void stop() { + if(activeView() ) { + activeView()->stop(); + } else { + // No view, no paging + slotStateChanged("pageOffState"); + } + } - /// Added by M.Zugaro to enable automatic forward paging - void page() { if ( isStill() ) slotStateChanged("pageOnState"); else slotStateChanged("pageOffState"); activeView()->page(); } - void stop() { if( activeView() ) { activeView()->stop(); slotStateChanged("pageOffState"); } } - void accelerate() { if( activeView()) activeView()->accelerate(); } - void decelerate() { if( activeView()) activeView()->decelerate(); } + /** Increase paging speed */ + void accelerate() { + if(activeView()) + activeView()->accelerate(); + } + + /** Decrease paging speed */ + void decelerate() { + if(activeView()) + activeView()->decelerate(); + } /**Called when an event has been modified. * @param providerName name use to identified the event provider containing the modified event. @@ -286,15 +322,15 @@ public Q_SLOTS: */ void initDisplay(QList* channelsToDisplay,bool autocenterChannels,QList offsets,QList channelGains, QList selectedChannels,QMap& skipStatus,int rasterHeight=-1,long duration = 1000,long startTime = 0,QString tabLabel = QString()); - + /** * queryClose is called by closeEvent */ bool queryClose(); - + void customEvent (QEvent* event); void closeEvent(QCloseEvent *event); - + private Q_SLOTS: void slotAbout(); @@ -302,18 +338,29 @@ private Q_SLOTS: /**Open a file and load it into the document.*/ void slotFileOpen(); +#ifdef WITH_CEREBUS + /**Open a network stream as a document. */ + void slotStreamOpen(); +#endif + /**Loads one or multiple cluster files.*/ void slotLoadClusterFiles(); + /** Updates view, because a new cluster file was loaded. */ + void slotClusterFileLoaded(const QString& fileID); + /**Closes the cluster file corresponding to the currently selected cluster group.*/ void slotCloseClusterFile(); - + /**Loads one or multiple event files.*/ void slotLoadEventFiles(); /**Creates an empty event file.*/ void slotCreateEventFile(); - + + /** Updates view, because a new event file was loaded or created. */ + void slotEventFileLoaded(const QString& fileID); + /**Closes the event file corresponding to the currently selected event group.*/ void slotCloseEventFile(); @@ -322,7 +369,7 @@ private Q_SLOTS: /**Closes the position file.*/ void slotClosePositionFile(); - + /**Opens a file from the recent files menu. */ void slotFileOpenRecent(const QString& url); @@ -330,7 +377,7 @@ private Q_SLOTS: * (number of channels, sampling rate of the dat file and eeg file). */ void slotFileProperties(); - + /**If need it, warns the user that the spike groups have been modified, then closes the actual file and displayss.*/ void slotFileClose(); @@ -358,7 +405,7 @@ private Q_SLOTS: * have the same amplification. */ void slotShowCalibration(); - + /**Changes the statusbar contents for the standard label permanently, used to indicate current actions. * @param text the text that is displayed in the statusbar. */ @@ -368,7 +415,7 @@ private Q_SLOTS: /*Slots for the tools menu.*/ /**Chooses the tool to select channels, enabling the user to select traces in order to move them.*/ void slotSelect(); - + /**Chooses the zoom tool, enabling the user to zoom.*/ void slotZoom(); @@ -379,7 +426,7 @@ private Q_SLOTS: /**Chooses the selection time tool, enabling the user to select a time frame (subset of the currently shown) * for which the traces are going to be displayed.*/ void slotSelectTime(); - + /**Chooses the tool to select an event, enabling the user to select an event in order to move or delete it.*/ void slotSelectEvent(); @@ -468,13 +515,13 @@ private Q_SLOTS: /**Selects all the clusters of the current palette except the clusters of artefact and noise * (clusters 0 and 1 respectively).*/ void slotSelectAllWO01(); - + /**Sets the mode of presentation in the current display, single or multiple columns.*/ void slotDisplayMode(); /**Displays or hides vertical lines to show the clusters.*/ void slotClustersVerticalLines(); - + /**Displays or hides a raster to show the clusters.*/ void slotClustersRaster(); @@ -486,7 +533,7 @@ private Q_SLOTS: /**Triggers the moves of the selected channels to the discard spike group.*/ void slotDiscardSpikeChannels(); - + /**The selected channels have been moved to the trash group. * @param discarded ids of the channels to move to the trash group. */ @@ -635,7 +682,7 @@ private Q_SLOTS: * @param clustersToSkip new list of clusters to skip while browsing */ void slotUpdateClustersToSkip(const QString &groupName, const QList& clustersToSkip); - + /**Marks the selected channels has keeped.*/ void slotKeepChannels(); @@ -666,6 +713,10 @@ private Q_SLOTS: void slotSaveRecentFiles(); + /// Added by M.Zugaro to enable automatic forward paging + void neuroscopeViewStarted() { slotStateChanged("pageOnState"); } + void neuroscopeViewStopped() { slotStateChanged("pageOffState"); } + private: void readSettings(); void initView(); @@ -697,7 +748,7 @@ private Q_SLOTS: QAction* addEventToolBarAction; QAction* positionViewToggle; QAction* showEventsInPositionView; - + QAction* mProperties; QAction* mLoadClusterFiles; QAction* mLoadEventFiles; @@ -752,6 +803,9 @@ private Q_SLOTS: QAction* mSaveAsAction; QAction* mCloseAction; QAction* mOpenAction; +#ifdef WITH_CEREBUS + QAction* mStreamAction; +#endif QAction* mUndo; QAction* mRedo; QAction* mViewStatusBar; @@ -776,7 +830,7 @@ private Q_SLOTS: * Inititalized in initItemPanel(). */ ChannelPalette* displayChannelPalette; - + /**spikeChannelPalette is the Widget containing the channel list to create the spike groups. * Inititalized in initItemPanel(). */ @@ -820,10 +874,10 @@ private Q_SLOTS: /**Flag to keep track of group modifications. */ bool groupsModified; - + /**Flag to keep track of color modifications. */ bool colorModified; - + /**Flag to keep track of event modifications. */ bool eventsModified; @@ -850,7 +904,7 @@ private Q_SLOTS: /**List storing the identifiers of the opened cluster files.*/ QStringList clusterFileList; - + /**List storing the identifiers of the opened event files.*/ QStringList eventFileList; diff --git a/src/neuroscope.ico b/src/neuroscope.ico new file mode 100644 index 0000000..d667d02 Binary files /dev/null and b/src/neuroscope.ico differ diff --git a/src/neuroscope.rc b/src/neuroscope.rc new file mode 100644 index 0000000..47e042f --- /dev/null +++ b/src/neuroscope.rc @@ -0,0 +1,2 @@ +// Set executable icon +IDI_ICON1 ICON "neuroscope.ico" diff --git a/src/neuroscopedoc.cpp b/src/neuroscopedoc.cpp index 09db48e..e1dc888 100644 --- a/src/neuroscopedoc.cpp +++ b/src/neuroscopedoc.cpp @@ -33,23 +33,48 @@ #include "neuroscope.h" #include "neuroscopeview.h" #include "tracesprovider.h" +#include "nsxtracesprovider.h" #include "traceview.h" #include "channelcolors.h" #include "neuroscopexmlreader.h" #include "parameterxmlmodifier.h" #include "parameterxmlcreator.h" #include "sessionxmlwriter.h" -#include "sessionInformation.h" +//#include "sessionInformation.h" #include "clustersprovider.h" +#include "nevclustersprovider.h" +#include "eventsprovider.h" +#include "neveventsprovider.h" #include "itemcolors.h" #include "itempalette.h" #include "positionsprovider.h" #include "imagecreator.h" #include "utilities.h" +#include "nwbreader.h" +#include "nwbtracesprovider.h" +#include "nwbeventsprovider.h" +#include "nwbclustersprovider.h" + +#ifdef WITH_CEREBUS +#include "cerebustraceprovider.h" +#endif + extern QString version; + +bool NeuroscopeDoc::bIsNearZero(double dVal) +{ + return (fabs(dVal) <= 0.0001) ? true : false; +} + +bool NeuroscopeDoc::bAreNearlyEqual(double dVal1, double dVal2) +{ + return (fabs(dVal1 - dVal2) <= 0.0001) ? true : false; +} + + NeuroscopeDoc::NeuroscopeDoc(QWidget* parent, ChannelPalette& displayChannelPalette, ChannelPalette& spikeChannelPalette, int channelNbDefault, double datSamplingRateDefault, double eegSamplingRateDefault, int initialOffset, int voltageRangeDefault, int amplificationDefault, float screenGainDefault, int resolutionDefault, int eventPosition, int clusterPosition, @@ -67,8 +92,8 @@ NeuroscopeDoc::NeuroscopeDoc(QWidget* parent, ChannelPalette& displayChannelPale voltageRangeDefault(voltageRangeDefault), amplificationDefault(amplificationDefault), isCommandLineProperties(false), - channelColorList(0L), - tracesProvider(0L), + channelColorList(nullptr /*0L*/), + tracesProvider(nullptr /*0L*/), parent(parent), nbSamplesDefault(nbSamples), peakSampleIndexDefault(peakSampleIndex), @@ -95,15 +120,6 @@ NeuroscopeDoc::NeuroscopeDoc(QWidget* parent, ChannelPalette& displayChannelPale voltageRange = voltageRangeDefault; amplification = amplificationDefault; screenGain = screenGainDefault; - acquisitionGainDefault = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolutionDefault)) - / static_cast(voltageRangeDefault * 1000)) - * amplificationDefault); - - gainDefault = static_cast(0.5 + screenGainDefault * acquisitionGainDefault); - - gain = gainDefault; - acquisitionGain = acquisitionGainDefault; resolution = resolutionDefault; this->initialOffset = initialOffsetDefault; this->nbSamples = nbSamplesDefault; @@ -118,8 +134,8 @@ NeuroscopeDoc::NeuroscopeDoc(QWidget* parent, ChannelPalette& displayChannelPale this->traceBackgroundImage = traceBackgroundImageDefault; //Temp - waveformLength = waveformLengthDefault = 1.6; - indexLength = indexLengthDefault = 0.8; + waveformLength = waveformLengthDefault = 1.6f; + indexLength = indexLengthDefault = 0.8f; } NeuroscopeDoc::~NeuroscopeDoc(){ @@ -196,8 +212,6 @@ void NeuroscopeDoc::closeDocument() voltageRange = voltageRangeDefault; amplification = amplificationDefault; screenGain = screenGainDefault; - gain = gainDefault; - acquisitionGainDefault = acquisitionGainDefault; resolution = resolutionDefault; initialOffset = initialOffsetDefault; isCommandLineProperties = false; @@ -223,6 +237,7 @@ void NeuroscopeDoc::closeDocument() channelsSpikeGroups.clear(); displayGroupsChannels.clear(); spikeGroupsChannels.clear(); + channelLabels.clear(); skipStatus.clear(); //Variables used for cluster and event providers @@ -235,72 +250,261 @@ void NeuroscopeDoc::closeDocument() if(channelColorList){ delete channelColorList; - channelColorList = 0L; + channelColorList = nullptr /*0L*/; delete tracesProvider; - tracesProvider = 0L; + tracesProvider = nullptr /*0L*/; } channelDefaultOffsets.clear(); } bool NeuroscopeDoc::isADocumentToClose(){ - return (channelColorList != 0L); + return (channelColorList != nullptr /*0L*/); } +QColor NeuroscopeDoc::makeClusterColor(int iGroup, bool bLinear) +{ + QColor color; -int NeuroscopeDoc::openDocument(const QString& url) + if(!bLinear && iGroup == 1) + color.setHsv(0,0,220);//Cluster 1 is always gray + else if(!bLinear && iGroup == 0) + color.setHsv(0,255,255);//Cluster 0 is always red + else + color.setHsv(static_cast(fmod(static_cast(iGroup)*7,36))*10,255,255); + + return color; +} + +void NeuroscopeDoc::nwbGetColors(QMap >& displayGroupsChannels, QMap& displayChannelsGroups, int channelNb, std::string hsFileName) { - qDebug()<<" int NeuroscopeDoc::openDocument(const QString& url)"< indexData(nbSamples,channelNb); + Array groupData(nbSamples,channelNb); - //We look for the general information: - // - the type of file: dat or eeg - // - the number of channels - // - the sampling rate for the dat file - // - the sampling rate for the eeg file - // - the composition of the groups of electrodes - // - the acquisition system resolution - //in the following locations and the following order: parameter file, session file, command line, defaults. - - //If there is information from the command line and also a parameter file or a session file, warn the user that - //the information will be taken from the file. - - //The session file also provides the following information (used only at the display level): - // - the color of the individual electrode, their group colors (display and spike) - // - the res, cluster and event files which have to be loaded - // - the global offset for all the traces. - // - the gain. - //for each display: - // - the display mode (single or multicolumn) - // - the electrodes to display and their individual offset - // - the spike display (raster, waveforms, vertical lines) - // - which spike, cluster and event to display - - - bool sessionFileExist = false; - QFileInfo urlFileInfo(url); - QString fileName = urlFileInfo.fileName(); - QStringList fileParts = fileName.split(QLatin1Char('.'), QString::SkipEmptyParts); - if(fileParts.count() < 2) - return INCORRECT_FILE; - qDebug()<<"NeuroscopeDoc::openDocument file correct"; - //QString extension; + NWBReader nwbr(hsFileName); + nwbr.getVoltageGroups(indexData, groupData, channelNb); + + displayGroupsChannels.clear(); + for (int i=0; i qi = QList() << iIndex; + displayGroupsChannels.insert(iGroup, qi); + } + } + + displayChannelsGroups.clear(); + ItemColors* nwbColors = new ItemColors(); + QList groupOne; + // Build some default group mappings, in case the file is incomplete + for (int i=0; icontains(iGroup)){ + color = nwbColors->color(iGroup); + } else { + color = makeClusterColor(iGroup, false); + } + nwbColors->append(static_cast(iGroup), color); + + displayChannelsGroups[iIndex] = iGroup; + + + channelColorList->append(i,color,color,color); + channelsSpikeGroups.insert(i, -1); + channelDefaultOffsets.insert(i, 0); + groupOne.append(i); + } + spikeGroupsChannels.insert(-1, groupOne); + delete nwbColors; + +} + +void NeuroscopeDoc::warnCommandLineProp(QString trWarning) +{ + if(isCommandLineProperties){ + QApplication::restoreOverrideCursor(); + QMessageBox::information(nullptr /*0*/, tr("Warning!"), trWarning); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + } +} + + + +void NeuroscopeDoc::confirmParams() +{ + bool isaDatFile = true; + if (extension != "dat") + isaDatFile = false; + + //Show a dialog to inform the user what are the parameters which will be used. + static_cast(parent)->displayFileProperties(channelNb, samplingRate, resolution, initialOffset, voltageRange, + amplification, screenGain, nbSamples, peakSampleIndex, videoSamplingRate, videoWidth, videoHeight, backgroundImage, + rotation, flip, datSamplingRate, isaDatFile, drawPositionsOnBackground, traceBackgroundImage); + + if (extension == "eeg") + eegSamplingRate = samplingRate; + //if the extension is not dat, the sampling rate for the dat file while be the default one. + else if (extension == "dat") + datSamplingRate = samplingRate; + else + extensionSamplingRates.insert(extension, samplingRate); + + //qDebug() << "end confirmParams()"; +} + +int NeuroscopeDoc::openNWBDocument(const QString& url) +{ + // Neurodata Without Borders Data + NWBTracesProvider* nwbTracesProvider = new NWBTracesProvider(url); + + if(!nwbTracesProvider->init()) { + return OPEN_ERROR; + } + + this->tracesProvider = nwbTracesProvider; + + // Warn user if command line properties were used + warnCommandLineProp(tr("You are opening a NWB file, the command line information will be discarded.")); + + + // Extract important information from file + this->channelNb = nwbTracesProvider->getNbChannels(); + this->samplingRate = nwbTracesProvider->getSamplingRate(); + this->resolution = nwbTracesProvider->getResolution(); + this->channelLabels = nwbTracesProvider->getLabels(); + + //extensionSamplingRates.insert(extension,samplingRate); + + nwbGetColors(displayGroupsChannels, displayChannelsGroups, channelNb, docUrl.toUtf8().constData()); + + confirmParams(); + + // Create the view with some sensible defaults + QList* channelsToDisplay = new QList(); // Yeah, I know! Why? + for(int i = 0 ; i < this->channelNb; ++i){ + channelsToDisplay->append(i); + } + QList channelOffsets = this->channelDefaultOffsets.values(); + QList channelGains; + QList selectedChannels; + emit loadFirstDisplay( + channelsToDisplay, + false, // cluster vertical lines + false, // cluster raster + true, // cluster waveforms + false, // show label + false, // multiple columns + false, // gray mode + false, // autocenter channels + channelOffsets, + channelGains, + selectedChannels, + this->skipStatus, + 0, // initial start time of trace view + 1000, // initial window size of trace view + QString(), // tab label + false, // position view + -1, // raster height + false // show events in position view + ); + + getAssociatedFileNames(); + + return OK; +} + +int NeuroscopeDoc::openNSXDocument(const QString& url) +{ + NSXTracesProvider* nsxTracesProvider = new NSXTracesProvider(url); + + if(!nsxTracesProvider->init()) { + return OPEN_ERROR; + } - //Treat the case when the selected file is a neuroscope session file or a par file. + this->tracesProvider = nsxTracesProvider; + + // Warn user if command line properties were used + warnCommandLineProp(tr("You are opening a NSX file, the command line information will be discarded.")); + + + // Extract important information from file + this->channelNb = nsxTracesProvider->getNbChannels(); + this->samplingRate = nsxTracesProvider->getSamplingRate(); + this->resolution = nsxTracesProvider->getResolution(); + this->channelLabels = nsxTracesProvider->getLabels(); + + //extensionSamplingRates.insert(extension,samplingRate); + + // Set up display and spike groups + QList displayGroup; + QColor color = QColor::fromHsv(210, 255, 255); // default blue + for (int i = 0; i < channelNb; ++i){ + // All channels have the same color, no offset and no skip status. + this->channelColorList->append(i, color); + this->channelDefaultOffsets.insert(i, 0); + + // Put all channels in the same display group. + this->displayChannelsGroups.insert(i, 1); + displayGroup.append(i); + + // Put each channel in its own spiking group. + this->channelsSpikeGroups.insert(i, i + 1); + QList group; + group.append(i); + this->spikeGroupsChannels.insert(i + 1, group); + } + this->displayGroupsChannels.insert(1, displayGroup); + + + // If skipStatus is empty, set the default status to 0 + if(skipStatus.isEmpty()){ + for(int i = 0; i < channelNb; ++i) + skipStatus.insert(i,false); + } + + //Use the channel default offsets + emit noSession(channelDefaultOffsets, skipStatus); + + return OK; +} + +// modifies baseName, docUrl +int NeuroscopeDoc::treatParamSessionFile(QString fileName, QStringList fileParts, QFileInfo urlFileInfo) +{ + //Treat the case when the selected file is a neuroscope session file (.nrs) or a par file (.xml). if(fileName.contains(QLatin1String(".nrs")) || fileName.contains(QLatin1String(".xml"))){ if((fileName.contains(".nrs") && fileParts[fileParts.count() - 1] != "nrs") || (fileName.contains(".xml") && fileParts[fileParts.count() - 1] != "xml")) { qDebug()<<" NeuroscopeDoc::openDocument INCORRECT FILE"; return INCORRECT_FILE; } else { baseName = fileParts.first(); - for(uint i = 1;i < fileParts.count() - 1; ++i) + for(int i = 1;i < fileParts.count() - 1; ++i) baseName += "." + fileParts.at(i); - //As all the files with the same base name share the same session and par files, ask the user to selected the desire one. + //As all the files with the same base name share the same session and par files, ask the user to selected the desired one. QString startUrl = urlFileInfo.absolutePath() + QDir::separator() + baseName; //QString filter = baseName + ".dat " + " " + baseName + ".eeg" + " " + baseName + ".fil"; - QString filter(tr("Data File (*.dat *.lfp *.eeg *.fil);;All files (*.*)")); + QString filter(tr("Data File (*.dat *.lfp *.eeg *.fil *.nwb);;All files (*.*)")); //filter.append(baseName + ".*"); const QString openUrl = QFileDialog::getOpenFileName(parent, tr("Open Data File..."),startUrl,filter); @@ -318,11 +522,86 @@ int NeuroscopeDoc::openDocument(const QString& url) } } } + return OK; +} + + +int NeuroscopeDoc::readParameterFile(const QString& parFileUrl) +{ + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + if(reader.parseFile(parFileUrl,NeuroscopeXmlReader::PARAMETER)){ + //Load the general info + loadDocumentInformation(reader); + + //try to get the extension information from the parameter file (prior to the 1.2.3 version, the information was + //stored in the session file) + extensionSamplingRates = reader.getSampleRateByExtension(); + qDebug()<<" NeuroscopeDoc::openDocument NeuroscopeXmlReader::PARAMETER"; + reader.closeFile(); + } + else{ + qDebug()<<" NeuroscopeDoc::openDocument PARSE_ERROR"; + return PARSE_ERROR; + } + return OK; +} + +int NeuroscopeDoc::readSessionFileSampling(const QString& sessionUrl) +{ + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + qDebug()<<"1 sessionUrl"< groupOne; + color.setHsv(210, 255, 255); + for (int i = 0; i < channelNb; ++i) { + channelColorList->append(i, color); + displayChannelsGroups.insert(i, 1); + channelsSpikeGroups.insert(i, -1); + groupOne.append(i); + channelDefaultOffsets.insert(i, 0); + } + displayGroupsChannels.insert(1, groupOne); + spikeGroupsChannels.insert(-1, groupOne); +} - NeuroscopeXmlReader reader = NeuroscopeXmlReader(); - if(reader.parseFile(parFileUrl,NeuroscopeXmlReader::PARAMETER)){ - //Load the general info - loadDocumentInformation(reader); +int NeuroscopeDoc::openDocumentHasParam(bool sessionFileExist) +{ + warnCommandLineProp(tr("A parameter file has be found, the command line\n" + "information will be discarded and the parameter file information will be used instead.")); - //try to get the extension information from the parameter file (prior to the 1.2.3 version, the information was - //store in the session file) - extensionSamplingRates = reader.getSampleRateByExtension(); - qDebug()<<" NeuroscopeDoc::openDocument NeuroscopeXmlReader::PARAMETER"; + int iRetParam = readParameterFile(parameterUrl); // was parFileUrl + if (iRetParam != OK) + return iRetParam; + + //Is there a session file? + if(sessionFileExist){ + int iRetSession = readSessionFileSampling(sessionUrl); + if (iRetSession != OK) + return iRetSession; + } + + //If the file extension is not a .dat or .eeg look up the sampling rate for + //the extension. If no sampling rate is available, prompt the user for the information. + getNonDatSampling(); + + //Create the tracesProvider with the information gathered before. + tracesProvider = new TracesProvider(docUrl, channelNb, resolution, voltageRange, amplification, samplingRate, initialOffset); + + //Is there a session file? + if(sessionFileExist) { + //qDebug()<<"2 sessionUrl"< return an error. + if(reader.getVersion().isEmpty() || reader.getVersion() == "1.2.2"){ + warnCommandLineProp(tr("A session file has been found, the command line " + "information will be discarded and the session file information will be used instead.")); + + //Load the general info + loadDocumentInformation(reader); + }else { + qDebug()<<" NeuroscopeDoc::openDocument MISSING FILE"; + return MISSING_FILE; } - //If the file extension is not a .dat or .eeg look up the sampling rate for + //Load the extension-sampling rate maping (prior to the 1.2.3 version, the information was store in the session file) + extensionSamplingRates = reader.getSampleRateByExtension(); + + //If the file extension is not a .nrs, .dat or .eeg look up the sampling rate for //the extension. If no sampling rate is available, prompt the user for the information. - if(extension != "eeg" && extension != "dat" && extension != "xml"){ - if(extensionSamplingRates.contains(extension)) { - samplingRate = extensionSamplingRates[extension]; - //Prompt the user - } else { - QApplication::restoreOverrideCursor(); - - QString currentSamplingRate = QInputDialog::getText(0,tr("Sampling Rate"),tr("Type in the sampling rate for the current document"),QLineEdit::Normal,QString::number(datSamplingRate)); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - if(!currentSamplingRate.isEmpty()) - samplingRate = currentSamplingRate.toDouble(); - else - samplingRate = datSamplingRate;//default - extensionSamplingRates.insert(extension,samplingRate); - } - } else { - if(extension == "eeg") - samplingRate = eegSamplingRate; - //Assign the default, dat sampling rate - else - samplingRate = datSamplingRate; - } + getNonDatSampling(); //Create the tracesProvider with the information gather before. - tracesProvider = new TracesProvider(docUrl,channelNb,resolution,samplingRate,initialOffset); + tracesProvider = new TracesProvider(docUrl, channelNb, resolution, voltageRange, amplification, samplingRate, initialOffset); - //Is there a session file? - if(sessionFileExist) { - qDebug()<<" NeuroscopeDoc::openDocument sessionfile exit"; - loadSession(reader); - reader.closeFile(); - } + //Load the session information + loadSession(reader); + + qDebug()<<" NeuroscopeDoc::openDocument CLOSE FILE"; + reader.closeFile(); } - //there is no parameter file - else{ - //look up in the session file - if(sessionFileInfo.exists()){ - sessionFileExist = true; - NeuroscopeXmlReader reader = NeuroscopeXmlReader(); - - if(reader.parseFile(sessionUrl,NeuroscopeXmlReader::SESSION)){ - //get the file version. If it is "" or "1.2.2, the documentation information can be stored in the session file, - //read it from there, otherwise it is an error. After version 1.2.2 a parameter file should always exit at the same time that the session - //file => return an error. - if(reader.getVersion().isEmpty() || reader.getVersion() == "1.2.2"){ - if(isCommandLineProperties){ - QApplication::restoreOverrideCursor(); - QMessageBox::information(0, tr("Warning!"),tr("A session file has been found, the command line " - "information will be discarded and the session file information will be used instead.")); - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - } - - //Load the general info - loadDocumentInformation(reader); - }else { - qDebug()<<" NeuroscopeDoc::openDocument MISSING FILE"; - return MISSING_FILE; - } + else + return PARSE_ERROR; - //Load the extension-sampling rate maping (prior to the 1.2.3 version, the information was store in the session file) - extensionSamplingRates = reader.getSampleRateByExtension(); - - //If the file extension is not a .nrs, .dat or .eeg look up the sampling rate for - //the extension. If no sampling rate is available, prompt the user for the information. - if(extension != "eeg" && extension != "dat" && extension != "xml"){ - if(extensionSamplingRates.contains(extension)) - samplingRate = extensionSamplingRates[extension]; - //Prompt the user - else{ - QApplication::restoreOverrideCursor(); - QString currentSamplingRate = QInputDialog::getText(0,tr("Sampling Rate"),tr("Type in the sampling rate for the current document"),QLineEdit::Normal,QString::number(datSamplingRate)); - - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - if(!currentSamplingRate.isEmpty()) - samplingRate = currentSamplingRate.toDouble(); - else - samplingRate = datSamplingRate;//default - extensionSamplingRates.insert(extension,samplingRate); - } - } - else{ - if(extension == "eeg") - samplingRate = eegSamplingRate; - //Assign the default, dat sampling rate - else - samplingRate = datSamplingRate; - } + return OK; +} - //Create the tracesProvider with the information gather before. - tracesProvider = new TracesProvider(docUrl,channelNb,resolution,samplingRate,initialOffset); +//No parameter or session file. Use defaults and or command line information (any of the command line arguments have overwritten the default values). +int NeuroscopeDoc::openDocumentNoParamNoSession() +{ + confirmParams(); - //Load the session information - loadSession(reader); + //Create the tracesProvider with the information gather before. + tracesProvider = new TracesProvider(docUrl, channelNb, resolution, voltageRange, amplification, samplingRate, initialOffset); + qDebug() << "done new TracesProvider()"; - qDebug()<<" NeuroscopeDoc::openDocument CLOSE FILE"; - reader.closeFile(); - } - else return PARSE_ERROR; - } - //No parameter or session file. Use defaults and or command line information (any of the command line arguments have overwritten the default values). - else{ - bool isaDatFile = true; - if(extension != "dat") - isaDatFile = false; - //Show a dialog to inform the user what are the parameters which will be used. - static_cast(parent)->displayFileProperties(channelNb,samplingRate,resolution,initialOffset,voltageRange, - amplification,screenGain,nbSamples,peakSampleIndex,videoSamplingRate,videoWidth,videoHeight,backgroundImage, - rotation,flip,datSamplingRate,isaDatFile,drawPositionsOnBackground,traceBackgroundImage); - - if(extension == "eeg") - eegSamplingRate = samplingRate; - //if the extension is not dat, the sampling rate for the dat file while be the default one. - else if(extension == "dat") - datSamplingRate = samplingRate; - else - extensionSamplingRates.insert(extension,samplingRate); + //No group of channels exist, put all the channels in the same group (1 for the display palette and + //-1 (the trash group) for the spike palette) and assign them the same blue color. + //Build the channelColorList and channelDefaultOffsets (default is 0) + makeOneBlueGroup(); + return OK; +} - //Create the tracesProvider with the information gather before. - tracesProvider = new TracesProvider(docUrl,channelNb,resolution,samplingRate,initialOffset); +int NeuroscopeDoc::openDocument(const QString& url) +{ + qDebug()<<" int NeuroscopeDoc::openDocument(const QString& url)"< groupOne; - color.setHsv(210,255,255); - for(int i = 0; i < channelNb; ++i){ - channelColorList->append(i,color); - displayChannelsGroups.insert(i,1); - channelsSpikeGroups.insert(i,-1); - groupOne.append(i); - channelDefaultOffsets.insert(i,0); - } - displayGroupsChannels.insert(1,groupOne); - spikeGroupsChannels.insert(-1,groupOne); + // Check if this is a Neurodata Without Borders Data file + if(fileName.contains(QRegExp("\\.nwb", Qt::CaseInsensitive))) { + extension = "nwb"; + return openNWBDocument(url); + } + + // Check if this is a Blackrock NSX file + if(fileName.contains(QRegExp("\\.ns\\d"))) { + extension = "nsx"; + return openNSXDocument(url); + } + + //We look for the general information: + // - the type of file: dat or eeg + // - the number of channels + // - the sampling rate for the dat file + // - the sampling rate for the eeg file + // - the composition of the groups of electrodes + // - the acquisition system resolution + //in the following locations and the following order: parameter file, session file, command line, defaults. + + //If there is information from the command line and also a parameter file or a session file, warn the user that + //the information will be taken from the file. + + //The session file also provides the following information (used only at the display level): + // - the color of the individual electrode, their group colors (display and spike) + // - the res, cluster and event files which have to be loaded + // - the global offset for all the traces. + // - the gain. + //for each display: + // - the display mode (single or multicolumn) + // - the electrodes to display and their individual offset + // - the spike display (raster, waveforms, vertical lines) + // - which spike, cluster and event to display + + + QStringList fileParts = fileName.split(QLatin1Char('.'), QString::SkipEmptyParts); + if(fileParts.count() < 2) + return INCORRECT_FILE; + qDebug()<<"NeuroscopeDoc::openDocument file correct"; + + + //Treat the case when the selected file is a neuroscope session file (.nrs) or a par file (.xml). + // Possibly modifies baseName, docUrl + int iRetPS = treatParamSessionFile(fileName, fileParts, urlFileInfo); + if (iRetPS != OK) + return iRetPS; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); + getAssociatedFileNames(); + QFileInfo parFileInfo = QFileInfo(parameterUrl); // .xml file + QFileInfo sessionFileInfo = QFileInfo(sessionUrl); // .nrs file + bool sessionFileExist = (sessionFileInfo.exists()) ? true : false; - gain = static_cast(0.5 + screenGain * acquisitionGain); + + //Look up in the parameter file + // xml parameter file + if(parFileInfo.exists()){ + int iRetP = openDocumentHasParam(sessionFileExist); + if (iRetP != OK) + return iRetP; + } + //there is no parameter file + else{ + //look up in the session file, nrs + if(sessionFileExist){ + int iRetS = openDocumentNoParamHasSession(); + if (iRetS != OK) + return iRetS; + } + //No parameter or session file. Use defaults and or command line information (any of the command line arguments have overwritten the default values). + else{ + openDocumentNoParamNoSession(); } } @@ -538,6 +825,191 @@ int NeuroscopeDoc::openDocument(const QString& url) return OK; } +#ifdef WITH_CEREBUS +bool NeuroscopeDoc::openStream(CerebusTracesProvider::SamplingGroup group) { + // Open network stream + CerebusTracesProvider* cerebusTracesProvider = new CerebusTracesProvider(group); + + if(!cerebusTracesProvider->init()) { + QMessageBox::critical(0, tr("Error!"), tr("Could not open network stream: %1").arg(QString::fromStdString(cerebusTracesProvider->getLastErrorMessage()))); + return false; + } + this->docUrl = "cerebus.nsx"; + this->tracesProvider = cerebusTracesProvider; + + // Warn user if command line properties were used + if(this->isCommandLineProperties){ + QApplication::restoreOverrideCursor(); + QMessageBox::information(0, tr("Warning!"),tr("You are opening a network stream, all supplied command line options will be discarded.")); + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + } + + // Extract important information from stream + this->channelNb = cerebusTracesProvider->getNbChannels(); + this->samplingRate = cerebusTracesProvider->getSamplingRate(); + this->resolution = cerebusTracesProvider->getResolution(); + this->channelLabels = cerebusTracesProvider->getLabels(); + + // Set up display and spike groups + QList displayGroup; + QColor color = QColor::fromRgb(55, 126, 184); // light blue + this->channelColorList = new ChannelColors(); + for (int i = 0; i < channelNb; ++i){ + // All channels have the same color, no offset and no skip status. + this->channelColorList->append(i, color); + this->channelDefaultOffsets.insert(i, 0); + this->skipStatus.insert(i, false); + + // Put all channels in the same display group. + this->displayChannelsGroups.insert(i, 1); + displayGroup.append(i); + + // Put each channel in its own spiking group. + this->channelsSpikeGroups.insert(i, i + 1); + QList group; + group.append(i); + this->spikeGroupsChannels.insert(i + 1, group); + } + this->displayGroupsChannels.insert(1, displayGroup); + + // Create the view with some sensible defaults + QList* channelsToDisplay = new QList(); // Yeah, I know! Why? + for(int i = 0 ; i < this->channelNb; ++i){ + channelsToDisplay->append(i); + } + QList channelOffsets = this->channelDefaultOffsets.values(); + QList channelGains; + QList selectedChannels; + emit loadFirstDisplay( + channelsToDisplay, + false, // cluster vertical lines + false, // cluster raster + true, // cluster waveforms + false, // show label + false, // multiple columns + false, // gray mode + false, // autocenter channels + channelOffsets, + channelGains, + selectedChannels, + this->skipStatus, + 0, // initial start time of trace view + 1000, // initial window size of trace view + QString(), // tab label + false, // position view + -1, // raster height + false // show events in position view + ); + + // Integrate spike event data + QList list = cerebusTracesProvider->getClusterProviders(); + + // This a kind of ugly solution, but unless data providers are reworked, there is no proper place for this: + QList groupToPeakIndex; + groupToPeakIndex << 0 << 1 << 1 << 1 << 4 << 12; + QList groupToSampelCount; + groupToSampelCount << 0 << 4 << 4 << 4 << 16 << 48; + + // Set spike event sample count and peak postition + this->peakSampleIndex = groupToPeakIndex[group]; + this->nbSamples = groupToSampelCount[group]; + + for(QList::iterator providerIterator = list.begin(); + providerIterator != list.end(); + providerIterator++) { + // Get all needed properties from cluster provider + ClustersProvider* clustersProvider = *providerIterator; + QString name = clustersProvider->getName(); + QList clusterList = clustersProvider->clusterIdList(); + + // Add cluster provider to internal structure + providers.insert(name, clustersProvider); + providerUrls.insert(name, QString("cerebus.") + name + QString(".nev")); + + // Genereate cluster colors (based on color brewer) + ItemColors* clusterColors = new ItemColors(); + + // Unclassified + clusterColors->append(0, QColor::fromRgb(153, 153, 153)); // gray + // Classified + clusterColors->append(1, QColor::fromRgb(247, 129, 191)); // pink + clusterColors->append(2, QColor::fromRgb(166, 86, 40)); // brown + clusterColors->append(3, QColor::fromRgb(255, 255, 51)); // yellow + clusterColors->append(4, QColor::fromRgb(152, 78, 163)); // purple + clusterColors->append(5, QColor::fromRgb(77, 175, 74)); // green + // Artifacts + clusterColors->append(254, QColor::fromRgb(255, 127, 0)); // orange + // Noise + clusterColors->append(255, QColor::fromRgb(228, 26, 28)); // red + + providerItemColors.insert(name, clusterColors); + + // Compute which cluster files give data for a given anatomical group + computeClusterFilesMapping(); + + // Informs the views than there is a new cluster provider. + // There should be only one view, since we only created one display. + QList skipList; + for(int i = 0; i < this->viewList->count(); ++i) { + this->viewList->at(i)->setClusterProvider( + clustersProvider, + name, + clusterColors, + true, // active + clusterList, + &(this->displayGroupsClusterFile), + &(this->channelsSpikeGroups), + this->peakSampleIndex - 1, + this->nbSamples - peakSampleIndex, + skipList + ); + } + + emit clusterFileLoaded(name); + } + + // Integrate digital and serial event data + EventsProvider* eventsProvider = cerebusTracesProvider->getEventProvider(); + + QString name = eventsProvider->getName(); + + this->lastLoadedProvider = name; + this->lastEventProviderGridX = eventsProvider->getDescriptionLength(); + this->providers.insert(name, eventsProvider); + this->providerUrls.insert(name, "cerebus.nev"); + + //Constructs the eventColorList and eventsToSkip + //An id is assign to each event, this id will be used internally in NeuroScope and in the session file. + ItemColors* eventColors = new ItemColors(); + QList eventsList; + QMap eventMap = eventsProvider->eventDescriptionIdMap(); + QMap::Iterator it; + for(it = eventMap.begin(); it != eventMap.end(); ++it){ + int hue = fmod(it.value() * 7.0, 36) * 10; + QColor color = QColor::fromHsv(hue, 255, 255); + //TODO: replace above 2 lines with QColor color = GetClusterColor(it.value(), true); + eventColors->append(it.value(), it.key(), color); + eventsList.append(it.value()); + } + this->providerItemColors.insert(name, eventColors); + + // Informs the views than there is a new event provider. + // There should be only one view, since we only created one display. + QList eventsToSkip; + for(int i = 0; i < viewList->count(); i++) { + viewList->at(i)->setEventProvider(eventsProvider, + name, + eventColors, + true, + eventsList, + eventsToSkip); + } + emit eventFileLoaded(name); + + return true; +} +#endif + bool NeuroscopeDoc::saveEventFiles(){ QMap::ConstIterator iterator; for(iterator = providerUrls.constBegin(); iterator != providerUrls.constEnd(); ++iterator){ @@ -843,53 +1315,121 @@ void NeuroscopeDoc::setBackgroundColor(const QColor& backgroundColor){ //Get the active view. NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); - //Ask the active view to take the modification into account immediately - activeView->showAllWidgets(); + //Ask the active view to take the modification into account immediately + activeView->showAllWidgets(); +} + + +void NeuroscopeDoc::setTraceBackgroundImage(const QString& traceBackgroundImagePath){ + traceBackgroundImage = traceBackgroundImagePath; + if(tracesProvider){ + QImage traceBackgroundImage(traceBackgroundImagePath); + + //The views are updated. If the image is null, the background will be a plain color (no image) + //Get the active view and make it the first to take the modification into account. + NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); + + //Notify all the views of the modification + for(int i = 0; icount(); ++i) { + NeuroscopeView* view = viewList->at(i); + + if(view != activeView) + view->updateTraceBackgroundImage(traceBackgroundImage,false); + else + view->updateTraceBackgroundImage(traceBackgroundImage,true); + } + } +} + + +void NeuroscopeDoc::setInitialOffset(int offset){ + initialOffset = offset; + + if(tracesProvider){ + //Inform the tracesProvider + tracesProvider->setOffset(offset); + + //Get the active view and make it the first to take the modification into account. + NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); + activeView->documentFeaturesModified(); + + //Notify all the views of the modification + for(int i = 0; icount(); ++i) { + NeuroscopeView* view = viewList->at(i); + if(view != activeView) view->documentFeaturesModified(); + } + + //Ask the active view to take the modification into account immediately + activeView->updateViewContents(); + } +} + +void NeuroscopeDoc::setVoltageRange(int range) { + this->voltageRange = range; + + if(tracesProvider){ + //Inform the tracesProvider + tracesProvider->setVoltageRange(range); + + //Get the active view and make it the first to take the modification into account. + NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); + activeView->documentFeaturesModified(); + + //Notify all the views of the modification + + for(int i = 0; icount(); ++i) { + NeuroscopeView* view = viewList->at(i); + if(view != activeView) view->documentFeaturesModified(); + } + + //Ask the active view to take the modification into account immediately + activeView->updateViewContents(); + } } +void NeuroscopeDoc::setAmplification(int amplification){ + this->amplification = amplification; -void NeuroscopeDoc::setTraceBackgroundImage(const QString& traceBackgroundImagePath){ - traceBackgroundImage = traceBackgroundImagePath; if(tracesProvider){ - QImage traceBackgroundImage(traceBackgroundImagePath); + //Inform the tracesProvider + tracesProvider->setAmplification(amplification); - //The views are updated. If the image is null, the background will be a plain color (no image) //Get the active view and make it the first to take the modification into account. NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); + activeView->documentFeaturesModified(); //Notify all the views of the modification + for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - - if(view != activeView) - view->updateTraceBackgroundImage(traceBackgroundImage,false); - else - view->updateTraceBackgroundImage(traceBackgroundImage,true); + if(view != activeView) view->documentFeaturesModified(); } + + //Ask the active view to take the modification into account immediately + activeView->updateViewContents(); } } -void NeuroscopeDoc::setInitialOffset(int offset){ - initialOffset = offset; +void NeuroscopeDoc::setScreenGain(float screenGain){ + this->screenGain = screenGain; if(tracesProvider){ - //Inform the tracesProvider - tracesProvider->setOffset(offset); - //Get the active view and make it the first to take the modification into account. NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); - activeView->documentFeaturesModified(); + activeView->setGains(screenGain); //Notify all the views of the modification for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->documentFeaturesModified(); + + if(view != activeView) view->setGains(screenGain); } //Ask the active view to take the modification into account immediately activeView->updateViewContents(); } + } void NeuroscopeDoc::setGains(int voltageRange,int amplification,float screenGain){ @@ -897,23 +1437,25 @@ void NeuroscopeDoc::setGains(int voltageRange,int amplification,float screenGain this->amplification = amplification; this->screenGain = screenGain; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - gain = static_cast(0.5 + screenGain * acquisitionGain); if(tracesProvider){ + //Inform the tracesProvider + tracesProvider->setAmplification(amplification); + tracesProvider->setVoltageRange(voltageRange); + //Get the active view and make it the first to take the modification into account. NeuroscopeView* activeView = dynamic_cast(parent)->activeView(); - activeView->setGains(gain,acquisitionGain); + activeView->setGains(screenGain); + activeView->documentFeaturesModified(); //Notify all the views of the modification for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->setGains(gain,acquisitionGain); + if(view != activeView) { + view->setGains(screenGain); + view->documentFeaturesModified(); + } } //Ask the active view to take the modification into account immediately @@ -1022,6 +1564,7 @@ void NeuroscopeDoc::setChannelNb(int nb){ channelsSpikeGroups.clear(); displayGroupsChannels.clear(); spikeGroupsChannels.clear(); + channelLabels.clear(); channelColorList->removeAll(); displayChannelPalette.reset(); spikeChannelPalette.reset(); @@ -1039,11 +1582,13 @@ void NeuroscopeDoc::setChannelNb(int nb){ displayGroupsChannels.insert(1,groupOne); spikeGroupsChannels.insert(-1,groupOne); + channelLabels = tracesProvider->getLabels(); + //Update and show the channel Palettes. - displayChannelPalette.createChannelLists(channelColorList,&displayGroupsChannels,&displayChannelsGroups); + displayChannelPalette.createChannelLists(channelColorList,&displayGroupsChannels,&displayChannelsGroups,&channelLabels); displayChannelPalette.updateShowHideStatus(groupOne,false); - spikeChannelPalette.createChannelLists(channelColorList,&spikeGroupsChannels,&channelsSpikeGroups); + spikeChannelPalette.createChannelLists(channelColorList,&spikeGroupsChannels,&channelsSpikeGroups,&channelLabels); spikeChannelPalette.updateShowHideStatus(groupOne,false); //Resize the panel @@ -1069,81 +1614,28 @@ void NeuroscopeDoc::setChannelNb(int nb){ } } -void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ - int resolutionRead = reader.getResolution(); - if(resolutionRead != 0) resolution = resolutionRead; - int channelNbRead = reader.getNbChannels(); - if(channelNbRead != 0) channelNb = channelNbRead; - double datSamplingRateRead = reader.getSamplingRate(); - if(datSamplingRateRead != 0) datSamplingRate = datSamplingRateRead; - upsamplingRate = reader.getUpsamplingRate(); - double eegSamplingRateRead = reader.getLfpInformation(); - if(eegSamplingRateRead != 0) eegSamplingRate = eegSamplingRateRead; - //the sampling rate for the video is store in the section file extension/sampling rate - int videoWidthRead = reader.getVideoWidth(); - if(videoWidthRead != 0) videoWidth = videoWidthRead; - int videoHeightRead = reader.getVideoHeight(); - if(videoHeightRead != 0) videoHeight = videoHeightRead; - drawPositionsOnBackground = reader.getTrajectory(); - - //The background image information is stored in the parameter file starting with the version 1.2.3 - if(reader.getType() == NeuroscopeXmlReader::PARAMETER){ - if(reader.getBackgroundImage() != "-") - backgroundImage = reader.getBackgroundImage(); - - if(!backgroundImage.isEmpty()){ - QFileInfo fileInfo = QFileInfo(backgroundImage); - if(!fileInfo.exists()){ - QString imageUrl(backgroundImage); - QString fileName = QFileInfo(imageUrl).fileName(); - imageUrl = docUrl + QDir::separator() + fileName; - backgroundImage = QFileInfo(imageUrl).absolutePath(); - } - } - } - - //The background image information for the trace view is stored in the parameter file starting with the version 1.3.4 - if(reader.getType() == NeuroscopeXmlReader::PARAMETER){ - - if(reader.getTraceBackgroundImage() != "-") - traceBackgroundImage = reader.getTraceBackgroundImage(); - - if(!traceBackgroundImage.isEmpty()){ - QFileInfo fileInfo = QFileInfo(traceBackgroundImage); - if(!fileInfo.exists()){ - QString imageUrl = traceBackgroundImage; - QString fileName = QFileInfo(imageUrl).fileName(); - imageUrl = docUrl + QDir::separator() + fileName; - traceBackgroundImage = QFileInfo(imageUrl).absolutePath(); - } +QString NeuroscopeDoc::qsAddAbsolutePathIfNeeded(QString qsFileName) +{ + QString qsRet = qsFileName; + if(!qsFileName.isEmpty()){ + QFileInfo fileInfo = QFileInfo(qsFileName); + if(!fileInfo.exists()){ + QString imageUrl = qsFileName; + QString fileName = QFileInfo(imageUrl).fileName(); + imageUrl = docUrl + QDir::separator() + fileName; + qsRet = QFileInfo(imageUrl).absolutePath(); } } + return qsRet; +} - - if(reader.getVoltageRange() != 0) voltageRange = reader.getVoltageRange(); - //Neuroscope for the moment uses a unique amplification for all the channels - if(reader.getAmplification() != 0) amplification = reader.getAmplification(); - if(reader.getOffset() != 0) initialOffset = reader.getOffset(); - - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - reader.getAnatomicalDescription(channelNb,displayChannelsGroups,displayGroupsChannels,skipStatus); - - if(displayGroupsChannels.contains(0)) - spikeGroupsChannels.insert(0,displayGroupsChannels[0]); - reader.getSpikeDescription(channelNb,channelsSpikeGroups,spikeGroupsChannels); - - //compute which cluster files give data for a given anatomical group - computeClusterFilesMapping(); - +void NeuroscopeDoc::BuildChannelColorList(QList &colorsList) +{ //Build the channelColorList - //the checkColors will be used that their is a color information for each channel. + //The checkColors list will be used (to ensure) that there is color information for each channel. QList checkColors; for(int i = 0; i < channelNb; ++i) checkColors.append(i); - QList colorsList = reader.getChannelDescription(); + //QList colorsList = reader.getChannelDescription(); if(!colorsList.isEmpty()){ QList::iterator colorIterator; for(colorIterator = colorsList.begin(); colorIterator != colorsList.end(); ++colorIterator){ @@ -1177,6 +1669,74 @@ void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ } } +} + + +double NeuroscopeDoc::dGetNonZeroVal(double dTryVal, double dDefault) +{ + return (bIsNearZero(dTryVal)) ? dDefault : dTryVal; +} +int NeuroscopeDoc::iGetNonZeroVal(int iTryVal, int iDefault) +{ + return (iTryVal==0) ? iDefault : iTryVal; +} + +QString NeuroscopeDoc::qsGetNonDashParameter(NeuroscopeXmlReader::fileType ft, QString qsTryVal, QString qsDefault) +{ + //The background image information is stored in the parameter file starting with the version 1.2.3 + QString qsRet = qsDefault; + if(ft == NeuroscopeXmlReader::PARAMETER){ + if(qsTryVal != "-") + qsRet = qsTryVal; + qsRet = qsAddAbsolutePathIfNeeded(qsRet); + } + return qsRet; +} + + +void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ + resolution = iGetNonZeroVal(reader.getResolution(), resolution); + channelNb = iGetNonZeroVal(reader.getNbChannels(), channelNb); + datSamplingRate = dGetNonZeroVal(reader.getSamplingRate(), datSamplingRate); + eegSamplingRate = dGetNonZeroVal(reader.getLfpInformation(), eegSamplingRate); + upsamplingRate = reader.getUpsamplingRate(); + + //the sampling rate for the video is store in the section file extension/sampling rate + videoWidth = iGetNonZeroVal(reader.getVideoWidth(), videoWidth); + videoHeight = iGetNonZeroVal(reader.getVideoHeight(), videoHeight); + drawPositionsOnBackground = reader.getTrajectory(); + + //The background image information is stored in the parameter file starting with the version 1.2.3 + backgroundImage = qsGetNonDashParameter(reader.getType(), reader.getBackgroundImage(), backgroundImage); + + //The background image information for the trace view is stored in the parameter file starting with the version 1.3.4 + traceBackgroundImage = qsGetNonDashParameter(reader.getType(), reader.getTraceBackgroundImage(), traceBackgroundImage); + + voltageRange = iGetNonZeroVal(reader.getVoltageRange(), voltageRange); + //Neuroscope for the moment uses a unique amplification for all the channels + amplification =iGetNonZeroVal(reader.getAmplification(), amplification); + initialOffset = iGetNonZeroVal(reader.getOffset(), initialOffset); + + reader.getAnatomicalDescription(channelNb,displayChannelsGroups,displayGroupsChannels,skipStatus); + + if(displayGroupsChannels.contains(0)) + spikeGroupsChannels.insert(0,displayGroupsChannels[0]); + reader.getSpikeDescription(channelNb,channelsSpikeGroups,spikeGroupsChannels); + + //compute which cluster files give data for a given anatomical group + computeClusterFilesMapping(); + + //Build the channelColorList + //The checkColors list will be used (to ensure) that there is color information for each channel. + QList colorsList = reader.getChannelDescription(); + BuildChannelColorList(colorsList); + + // TODO: Save and load this to/from file or trace provider + // Build channel label list + channelLabels.clear(); + for(int i = 0; i < channelNb; ++i) + channelLabels << QString::number(i); + //Build the list of channel default offsets reader.getChannelDefaultOffset(channelDefaultOffsets); //if no default offset are available in the file, set the default offset to 0 @@ -1192,9 +1752,8 @@ void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ } } - if(reader.getScreenGain() != 0) + if(!bIsNearZero(static_cast(reader.getScreenGain()))) screenGain = reader.getScreenGain(); - gain = static_cast(0.5 + screenGain * acquisitionGain); //For the moment Neuroscope stores it own values for the nbSamples and the peakSampleIndex inside the specific neuroscope tag. //Therefore Neuroscope uses the same values for all the groups @@ -1204,7 +1763,7 @@ void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ //New way: the nbSamples and peakSampleIndex are given in time. The sampling rate is used to compute the information. //If no upsampling exists <=> Old way - if(upsamplingRate == 0){ + if(bIsNearZero(upsamplingRate)){ //the upsampling is set to the sampling rate. upsamplingRate = datSamplingRate; int nbSamplesRead = reader.getNbSamples(); @@ -1214,8 +1773,8 @@ void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ } else { float waveformLengthRead = reader.getWaveformLength(); float indexLengthRead = reader.getPeakSampleLength(); - if(waveformLengthRead != 0) waveformLength = waveformLengthRead; - if(indexLengthRead != 0) indexLength = indexLengthRead; + if(!bIsNearZero(static_cast(waveformLengthRead))) waveformLength = waveformLengthRead; + if(!bIsNearZero(static_cast(indexLengthRead))) indexLength = indexLengthRead; //Compute the number of samples using the datSamplingRate. nbSamples = static_cast(static_cast(datSamplingRate / 1000) * waveformLength); @@ -1225,30 +1784,306 @@ void NeuroscopeDoc::loadDocumentInformation(NeuroscopeXmlReader reader){ } } +QList NeuroscopeDoc::qlGetClusterFileList(QList &anatomicalList) +{ + QList clusterFileList; + foreach(int iKey, spikeGroupsChannels.keys()) { + QList channels = spikeGroupsChannels[iKey]; + for (auto channelVal : channels) + { + if(anatomicalList.contains(channelVal)){ + clusterFileList.append(iKey); + break; + } + } + } + return clusterFileList; +} + void NeuroscopeDoc::computeClusterFilesMapping(){ displayGroupsClusterFile.clear(); //compute which cluster files give data for a given anatomical group QMap >::Iterator iterator; for(iterator = displayGroupsChannels.begin(); iterator != displayGroupsChannels.end(); ++iterator){ - QList clusterFileList; QList anatomicalList = iterator.value(); - QMap >::Iterator spikeGroupIterator; - for(spikeGroupIterator = spikeGroupsChannels.begin(); spikeGroupIterator != spikeGroupsChannels.end(); ++spikeGroupIterator){ - QList channels = spikeGroupIterator.value(); - QList::iterator channelIterator; - for(channelIterator = channels.begin(); channelIterator != channels.end(); ++channelIterator){ - if(anatomicalList.contains(*channelIterator)){ - clusterFileList.append(spikeGroupIterator.key()); - break; + QList clusterFileList = qlGetClusterFileList(anatomicalList); + + // The trash group (index 0) is always at the bottom in the display, so reindex it with the highest index. + // Otherwise take the key as the index. + int iIndex = (iterator.key() == 0) ? displayGroupsChannels.count() : iterator.key(); + displayGroupsClusterFile.insert(iIndex, clusterFileList); + } +} + +void NeuroscopeDoc::setSpikePresentationInfo(bool &verticalLines, bool &raster, bool &waveforms, const QList &spikeDisplayTypes) +{ + //info on the spike presentation + verticalLines = raster = waveforms = false; + + QList::ConstIterator typeIterator; + QList::ConstIterator typeIteratorEnd(spikeDisplayTypes.end()); + for(typeIterator = spikeDisplayTypes.constBegin(); typeIterator != typeIteratorEnd; ++typeIterator){ + if(*typeIterator == DisplayInformation::LINES) + verticalLines = true; + if(*typeIterator == DisplayInformation::RASTER) + raster = true; + if(*typeIterator == DisplayInformation::WAVEFORMS) + waveforms = true; + } +} + +void NeuroscopeDoc::fillGains_Offsets(QList &channelGains, QList &offsets, const QList positions) +{ + //Get the information concerning the channel positions (gain and offset) + QList::ConstIterator positionIterator; + QList::ConstIterator positionIteratorEnd(positions.constEnd()); + for (positionIterator = positions.constBegin(); positionIterator != positionIteratorEnd; ++positionIterator) { + int gain = static_cast(*positionIterator).getGain(); + int offset = static_cast(*positionIterator).getOffset(); + offsets.append(offset); + channelGains.append(gain); + } +} + +QList* NeuroscopeDoc::getChannelsToDisplay(const QList &channelIds) +{ + QList* channelsToDisplay = new QList(); + //Get the information concerning the channels shown in the display + QList::ConstIterator channelIterator; + QList::ConstIterator channelIteratorEnd(channelIds.constEnd()); + for (channelIterator = channelIds.constBegin(); channelIterator != channelIteratorEnd; ++channelIterator) { + channelsToDisplay->append(*channelIterator); + } + return channelsToDisplay; +} + +//Get the information concerning the channels selected in the display +QList NeuroscopeDoc::getSelectedChannels(const QList &selectedChannelIds) +{ + QList selectedChannels; + QList::ConstIterator channelSelectedIterator; + QList::ConstIterator channelSelectedIteratorEnd(selectedChannelIds.constEnd()); + for (channelSelectedIterator = selectedChannelIds.constBegin(); channelSelectedIterator != channelSelectedIteratorEnd; ++channelSelectedIterator) { + selectedChannels.append(*channelSelectedIterator); + } + return selectedChannels; +} + +void NeuroscopeDoc::loadSessionFirstFileCluster(QString fileUrl, + QMap > &selectedClusters, + QMap > &skippedClusters, + QMap &itemColors, + const QDateTime &lastModified, + bool &fistClusterFile, + QStringList &loadedClusterFiles) +{ +//if(fileType == SessionFile::CLUSTER){ + //If the file does not exist in the location specified in the session file (absolute path), look up in the directory + //where the session file is. This is useful if you moved your file or you backup them (<=> the absolute path is not good anymore) + QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); + if(!fileInfo.exists()){ + QList ids = selectedClusters[fileUrl]; + QList skippedIds = skippedClusters[fileUrl]; + selectedClusters.remove(fileUrl); + skippedClusters.remove(fileUrl); + QString fileName = QFileInfo(fileUrl).fileName(); + fileUrl = QFileInfo(sessionUrl).absolutePath() + QDir::separator() + fileName; + selectedClusters.insert(fileUrl,ids); + skippedClusters.insert(fileUrl,skippedIds); + } + OpenSaveCreateReturnMessage status = loadClusterFileForSession(fileUrl,itemColors,lastModified,fistClusterFile); + if(status == OK){ + loadedClusterFiles.append(lastLoadedProvider); + fistClusterFile = false; + } +//} // end if SessionFile::CLUSTER +} + +void NeuroscopeDoc::loadSessionFirstFileEvents(QString fileUrl, + QMap > &selectedEvents, + QMap > &skippedEvents, + QMap &itemColors, + const QDateTime &lastModified, + bool &fistEventFile, + QStringList &loadedEventFiles, + QString sessionUrl, + QMap< QString, QMap > &loadedEventItems) +{ + //if(fileType == SessionFile::EVENT){ + //If the file does not exist in the location specified in the session file (absolute path), look up in the directory + //where the session file is. This is useful if you moved your file or ypu backup them (<=> the absolute path is not good anymore) + QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); + if(!fileInfo.exists()){ + QList ids = selectedEvents[fileUrl]; + QList skippedIds = skippedEvents[fileUrl]; + selectedEvents.remove(fileUrl); + skippedEvents.remove(fileUrl); + QString fileName = QFileInfo(fileUrl).fileName(); + fileUrl = QFileInfo(sessionUrl).absolutePath() + QDir::separator() + fileName; + selectedEvents.insert(fileUrl,ids); + skippedEvents.insert(fileUrl,skippedIds); + } + OpenSaveCreateReturnMessage status = loadEventFileForSession(fileUrl,itemColors,lastModified,fistEventFile); + if(status == OK){ + loadedEventFiles.append(lastLoadedProvider); + fistEventFile = false; + QMap loadedItems; + QMap::ConstIterator it; + QMap::ConstIterator endColor(itemColors.constEnd()); + int index = 1; + for(it = itemColors.constBegin(); it != endColor; ++it){ + loadedItems.insert(it.key(),index); + index++; + } + loadedEventItems.insert(lastLoadedProvider,loadedItems); + } + // } // end SessionFile::EVENT +} + +void NeuroscopeDoc::loadSessionFirstFilePositions(QString fileUrl, + QString sessionUrl, + NeuroscopeXmlReader reader, + SessionFile &sessionFile, + QString loadedPositionFile) +{ + //if(fileType == SessionFile::POSITION){ + //If the file does not exist in the location specified in the session file (absolute path), look up in the directory + //where the session file is. This is useful if you moved your file or you backup them (<=> the absolute path is not good anymore) + QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); + if(!fileInfo.exists()){ + QString fileName = QFileInfo(fileUrl).fileName(); + fileUrl = QFileInfo(sessionUrl).absolutePath()+QDir::separator() + fileName; + } + + //Create the transformedBackground + //The background image information is stored in the parameter file starting with the version 1.2.3 + if(reader.getVersion().isEmpty() || reader.getVersion() == "1.2.2"){ + if(reader.getBackgroundImage() != "-") + backgroundImage = sessionFile.getBackgroundPath(); + if(!backgroundImage.isEmpty()){ + fileInfo = QFileInfo(backgroundImage); + if(!fileInfo.exists()){ + QString imageUrl= backgroundImage; + QString fileName = QFileInfo(imageUrl).fileName(); + imageUrl = sessionUrl + QDir::separator() + fileName; + backgroundImage = QFileInfo(imageUrl).absolutePath(); } } } - //The trash group (index 0) is always at the bottom in the display, so reindex it with the highest index. - if(iterator.key() == 0) displayGroupsClusterFile.insert(displayGroupsChannels.count(),clusterFileList); - else displayGroupsClusterFile.insert(iterator.key(),clusterFileList); + + OpenSaveCreateReturnMessage status = loadPositionFile(fileUrl); + if(status == OK){ + loadedPositionFile = lastLoadedProvider; + if(!backgroundImage.isEmpty() || (backgroundImage.isEmpty() && drawPositionsOnBackground)) + transformedBackground = transformBackgroundImage(); + static_cast(parent)->positionFileLoaded(); + } + // } // end if SessionFile::POSITION +} + +void NeuroscopeDoc::LoadSessionClusterFiles(QStringList &loadedClusterFiles, + QMap > &selectedClusters, + QMap > &skippedClusters, + NeuroscopeView* view ) +{ + QStringList::iterator providerIterator; + //Cluster files + for(providerIterator = loadedClusterFiles.begin(); providerIterator != loadedClusterFiles.end(); ++providerIterator){ + QString name = *providerIterator; + QString fileURL = providerUrls[name]; + QList clustersIds; + QList clustersIdsToSkip; + QList ids = selectedClusters[fileURL]; + QList skippedIds = skippedClusters[fileURL]; + QList clusterList = static_cast(providers[name])->clusterIdList(); + //only keep the cluster ids which are still present + QList::iterator shownClustersIterator; + for(shownClustersIterator = ids.begin(); shownClustersIterator != ids.end(); ++shownClustersIterator) + if(clusterList.contains(*shownClustersIterator)) clustersIds.append(*shownClustersIterator); + QList::iterator skippedClustersIterator; + for(skippedClustersIterator = skippedIds.begin(); skippedClustersIterator != skippedIds.end(); ++skippedClustersIterator) + if(clusterList.contains(*skippedClustersIterator)) clustersIdsToSkip.append(*skippedClustersIterator); + + //an unselected cluster has to be skipped, check and correct if need it + QList::iterator iterator; + for(iterator = clusterList.begin(); iterator != clusterList.end(); ++iterator) + if(!clustersIds.contains(*iterator) && !clustersIdsToSkip.contains(*iterator)) clustersIdsToSkip.append(*iterator); + //qSort(clustersIdsToSkip); + std::sort(clustersIdsToSkip.begin(), clustersIdsToSkip.end()); + view->setClusterProvider(static_cast(providers[name]),name,providerItemColors[name],true,clustersIds, + &displayGroupsClusterFile,&channelsSpikeGroups,peakSampleIndex - 1,nbSamples - peakSampleIndex,clustersIdsToSkip); + } + +} + +void NeuroscopeDoc::loadSessionEventFiles(QStringList &loadedEventFiles, + QMap > &selectedEvents, + QMap > &skippedEvents, + QMap< QString, QMap > &loadedEventItems, + NeuroscopeView* view) +{ + QStringList::iterator providerIterator; + //Event files + for(providerIterator = loadedEventFiles.begin(); providerIterator != loadedEventFiles.end(); ++providerIterator){ + QString name = *providerIterator; + QString fileURL = providerUrls[name]; + QList eventsIds; + QList eventsIdsToSkip; + QList ids = selectedEvents[fileURL]; + QList skippedIds = skippedEvents[fileURL]; + QMap eventMap = static_cast(providers[name])->eventIdDescriptionMap(); + //only keep the event ids which are still present + QMap loadedItems = loadedEventItems[name]; + ItemColors* eventColors = providerItemColors[name]; + QList::iterator shownEventsIterator; + for(shownEventsIterator = ids.begin(); shownEventsIterator != ids.end(); ++shownEventsIterator){ + EventDescription description = EventDescription(eventColors->itemLabelById(*shownEventsIterator)); + if(eventMap.contains(*shownEventsIterator) && loadedItems.contains(description) && loadedItems[description] == *shownEventsIterator) + eventsIds.append(*shownEventsIterator); + } + QList::iterator skippedEventsIterator; + for(skippedEventsIterator = skippedIds.begin(); skippedEventsIterator != skippedIds.end(); ++skippedEventsIterator){ + EventDescription description = EventDescription(eventColors->itemLabelById(*skippedEventsIterator)); + if(eventMap.contains(*skippedEventsIterator) && loadedItems.contains(description) && loadedItems[description] == *skippedEventsIterator) + eventsIdsToSkip.append(*skippedEventsIterator); + } + + //an unselected event has to be skipped, check and correct if need it + QMap::iterator iterator; + for(iterator = eventMap.begin(); iterator != eventMap.end(); ++iterator) + if(!eventsIds.contains(iterator.key()) && !eventsIdsToSkip.contains(iterator.key())) eventsIdsToSkip.append(iterator.key()); + //qSort(eventsIdsToSkip); + std::sort(eventsIdsToSkip.begin(), eventsIdsToSkip.end()); + + view->setEventProvider(static_cast(providers[name]),name,providerItemColors[name],true,eventsIds,eventsIdsToSkip); + } +} + +void NeuroscopeDoc::loadSessionPositionFile(QString loadedPositionFile, + bool isAPositionView, + long startTime, + long duration, + bool showEventsInPositionView, + NeuroscopeView* view) +{ + //Position file + QStringList::iterator providerIterator; + if(!loadedPositionFile.isEmpty()){ + if(isAPositionView){ + if(rotation != 90 && rotation != 270) + view->addPositionView(static_cast(providers[loadedPositionFile]),transformedBackground, dynamic_cast(parent)->getBackgroundColor(), + startTime,duration,videoWidth,videoHeight,showEventsInPositionView); + + //If there is a rotation of 90 or 270 degree, the with and height have to be inverted. + else + view->addPositionView(static_cast(providers[loadedPositionFile]),transformedBackground, dynamic_cast(parent)->getBackgroundColor(), + startTime,duration,videoHeight,videoWidth,showEventsInPositionView); + + } } } + void NeuroscopeDoc::loadSession(NeuroscopeXmlReader reader){ //Get the file video information if(reader.getRotation() != 0) { @@ -1271,51 +2106,30 @@ void NeuroscopeDoc::loadSession(NeuroscopeXmlReader reader){ QList::ConstIterator iterator; QList::ConstIterator end(displayList.constEnd()); for(iterator = displayList.constBegin(); iterator != end; ++iterator) { - QList offsets; - QList channelGains; - QList* channelsToDisplay = new QList(); - QList selectedChannels; - bool verticalLines = false; - bool raster = false; - bool waveforms = false; - bool multipleColumns = false; //Get the information store in DisplayInformation - DisplayInformation::mode presentationMode = static_cast(*iterator).getMode(); bool autocenterChannels = static_cast(*iterator).getAutocenterChannels(); long startTime = static_cast(*iterator).getStartTime(); long duration = static_cast(*iterator).getTimeWindow(); bool greyMode = static_cast(*iterator).getGreyScale(); - QList spikeDisplayTypes = static_cast(*iterator).getSpikeDisplayTypes(); int rasterHeight = static_cast(*iterator).getRasterHeight(); QMap > selectedClusters = static_cast(*iterator).getSelectedClusters(); //An id has been assigned to each event, this id will be used internally in NeuroScope and in the session file. - QMap > selectedEvents = static_cast(*iterator).getSelectedEvents(); - //QStringList shownSpikeFiles = static_cast(*iterator).getSelectedSpikeFiles(); + QMap > selectedEvents = static_cast(*iterator).getSelectedEvents(); QMap > skippedClusters = static_cast(*iterator).getSkippedClusters(); QMap > skippedEvents = static_cast(*iterator).getSkippedEvents(); - QList positions = static_cast(*iterator).getPositions(); - QList channelIds = static_cast(*iterator).getChannelIds(); - QList selectedChannelIds = static_cast(*iterator).getSelectedChannelIds(); QString tabLabel = static_cast(*iterator).getTabLabel(); bool showLabels = static_cast(*iterator).getLabelStatus(); bool showEventsInPositionView = static_cast(*iterator).isEventsDisplayedInPositionView(); //info on the trace presentation - if(presentationMode == DisplayInformation::MULTIPLE) - multipleColumns = true; + DisplayInformation::mode presentationMode = static_cast(*iterator).getMode(); + bool multipleColumns = (presentationMode == DisplayInformation::MULTIPLE) ? true: false; //info on the spike presentation - QList::ConstIterator typeIterator; - QList::ConstIterator typeIteratorEnd(spikeDisplayTypes.end()); - for(typeIterator = spikeDisplayTypes.constBegin(); typeIterator != typeIteratorEnd; ++typeIterator){ - if(*typeIterator == DisplayInformation::LINES) - verticalLines = true; - if(*typeIterator == DisplayInformation::RASTER) - raster = true; - if(*typeIterator == DisplayInformation::WAVEFORMS) - waveforms = true; - } + bool verticalLines = false, raster = false, waveforms = false; + QList spikeDisplayTypes = static_cast(*iterator).getSpikeDisplayTypes(); + setSpikePresentationInfo(verticalLines, raster, waveforms, spikeDisplayTypes); //Info regarding the positionView bool isAPositionView = static_cast(*iterator).isAPositionView(); @@ -1323,28 +2137,18 @@ void NeuroscopeDoc::loadSession(NeuroscopeXmlReader reader){ /*****************TO FINISH***************************/ //Get the information concerning the channel positions (gain and offset) - QList::ConstIterator positionIterator; - QList::ConstIterator positionIteratorEnd(positions.constEnd()); - for (positionIterator = positions.constBegin(); positionIterator != positionIteratorEnd; ++positionIterator) { - int gain = static_cast(*positionIterator).getGain(); - int offset = static_cast(*positionIterator).getOffset(); - offsets.append(offset); - channelGains.append(gain); - } + QList channelGains; + QList offsets; + QList positions = static_cast(*iterator).getPositions(); + fillGains_Offsets(channelGains, offsets, positions); //Get the information concerning the channels shown in the display - QList::ConstIterator channelIterator; - QList::ConstIterator channelIteratorEnd(channelIds.constEnd()); - for (channelIterator = channelIds.constBegin(); channelIterator != channelIteratorEnd; ++channelIterator) { - channelsToDisplay->append(*channelIterator); - } + QList channelIds = static_cast(*iterator).getChannelIds(); + QList* channelsToDisplay = getChannelsToDisplay(channelIds); //Get the information concerning the channels selected in the display - QList::ConstIterator channelSelectedIterator; - QList::ConstIterator channelSelectedIteratorEnd(selectedChannelIds.constEnd()); - for (channelSelectedIterator = selectedChannelIds.constBegin(); channelSelectedIterator != channelSelectedIteratorEnd; ++channelSelectedIterator) { - selectedChannels.append(*channelSelectedIterator); - } + QList selectedChannelIds = static_cast(*iterator).getSelectedChannelIds(); + QList selectedChannels = getSelectedChannels(selectedChannelIds); //Create the displays if(first){ @@ -1366,86 +2170,21 @@ void NeuroscopeDoc::loadSession(NeuroscopeXmlReader reader){ if(fileType == SessionFile::CLUSTER){ //If the file does not exist in the location specified in the session file (absolute path), look up in the directory //where the session file is. This is useful if you moved your file or you backup them (<=> the absolute path is not good anymore) - QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); - if(!fileInfo.exists()){ - QList ids = selectedClusters[fileUrl]; - QList skippedIds = skippedClusters[fileUrl]; - selectedClusters.remove(fileUrl); - skippedClusters.remove(fileUrl); - QString fileName = QFileInfo(fileUrl).fileName(); - fileUrl = QFileInfo(sessionUrl).absolutePath() + QDir::separator() + fileName; - selectedClusters.insert(fileUrl,ids); - skippedClusters.insert(fileUrl,skippedIds); - } - OpenSaveCreateReturnMessage status = loadClusterFile(fileUrl,itemColors,lastModified,fistClusterFile); - if(status == OK){ - loadedClusterFiles.append(lastLoadedProvider); - fistClusterFile = false; - } - } + loadSessionFirstFileCluster(fileUrl, selectedClusters, skippedClusters, itemColors, lastModified, + fistClusterFile, loadedClusterFiles); + } // end if SessionFile::CLUSTER if(fileType == SessionFile::EVENT){ //If the file does not exist in the location specified in the session file (absolute path), look up in the directory //where the session file is. This is useful if you moved your file or ypu backup them (<=> the absolute path is not good anymore) - QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); - if(!fileInfo.exists()){ - QList ids = selectedEvents[fileUrl]; - QList skippedIds = skippedEvents[fileUrl]; - selectedEvents.remove(fileUrl); - skippedEvents.remove(fileUrl); - QString fileName = QFileInfo(fileUrl).fileName(); - fileUrl = QFileInfo(sessionUrl).absolutePath() + QDir::separator() + fileName; - selectedEvents.insert(fileUrl,ids); - skippedEvents.insert(fileUrl,skippedIds); - } - OpenSaveCreateReturnMessage status = loadEventFile(fileUrl,itemColors,lastModified,fistEventFile); - if(status == OK){ - loadedEventFiles.append(lastLoadedProvider); - fistEventFile = false; - QMap loadedItems; - QMap::ConstIterator it; - QMap::ConstIterator endColor(itemColors.constEnd()); - int index = 1; - for(it = itemColors.constBegin(); it != endColor; ++it){ - loadedItems.insert(it.key(),index); - index++; - } - loadedEventItems.insert(lastLoadedProvider,loadedItems); - } - } + loadSessionFirstFileEvents(fileUrl, selectedEvents, skippedEvents, itemColors, lastModified, + fistEventFile, loadedEventFiles, sessionUrl, loadedEventItems); + } // end SessionFile::EVENT if(fileType == SessionFile::POSITION){ //If the file does not exist in the location specified in the session file (absolute path), look up in the directory //where the session file is. This is useful if you moved your file or you backup them (<=> the absolute path is not good anymore) - QFileInfo fileInfo = QFileInfo(fileUrl).absolutePath(); - if(!fileInfo.exists()){ - QString fileName = QFileInfo(fileUrl).fileName(); - fileUrl = QFileInfo(sessionUrl).absolutePath()+QDir::separator() + fileName; - } - - //Create the transformedBackground - //The background image information is stored in the parameter file starting with the version 1.2.3 - if(reader.getVersion().isEmpty() || reader.getVersion() == "1.2.2"){ - if(reader.getBackgroundImage() != "-") - backgroundImage = sessionFile.getBackgroundPath(); - if(!backgroundImage.isEmpty()){ - fileInfo = QFileInfo(backgroundImage); - if(!fileInfo.exists()){ - QString imageUrl= backgroundImage; - QString fileName = QFileInfo(imageUrl).fileName(); - imageUrl = sessionUrl + QDir::separator() + fileName; - backgroundImage = QFileInfo(imageUrl).absolutePath(); - } - } - } - - OpenSaveCreateReturnMessage status = loadPositionFile(fileUrl); - if(status == OK){ - loadedPositionFile = lastLoadedProvider; - if(!backgroundImage.isEmpty() || (backgroundImage.isEmpty() && drawPositionsOnBackground)) - transformedBackground = transformBackgroundImage(); - static_cast(parent)->positionFileLoaded(); - } - } - } + loadSessionFirstFilePositions(fileUrl,sessionUrl,reader, sessionFile, loadedPositionFile); + } // end if SessionFile::POSITION + } // next session iterator } else { static_cast(parent)->createDisplay(channelsToDisplay,verticalLines,raster,waveforms,showLabels,multipleColumns, greyMode,autocenterChannels,offsets,channelGains,selectedChannels,startTime,duration,rasterHeight,tabLabel); @@ -1460,79 +2199,13 @@ void NeuroscopeDoc::loadSession(NeuroscopeXmlReader reader){ view->ignoreWaveformInformation(); //Inform the view of the available providers - QStringList::iterator providerIterator; //Cluster files - for(providerIterator = loadedClusterFiles.begin(); providerIterator != loadedClusterFiles.end(); ++providerIterator){ - QString name = *providerIterator; - QString fileURL = providerUrls[name]; - QList clustersIds; - QList clustersIdsToSkip; - QList ids = selectedClusters[fileURL]; - QList skippedIds = skippedClusters[fileURL]; - QList clusterList = static_cast(providers[name])->clusterIdList(); - //only keep the cluster ids which are still present - QList::iterator shownClustersIterator; - for(shownClustersIterator = ids.begin(); shownClustersIterator != ids.end(); ++shownClustersIterator) - if(clusterList.contains(*shownClustersIterator)) clustersIds.append(*shownClustersIterator); - QList::iterator skippedClustersIterator; - for(skippedClustersIterator = skippedIds.begin(); skippedClustersIterator != skippedIds.end(); ++skippedClustersIterator) - if(clusterList.contains(*skippedClustersIterator)) clustersIdsToSkip.append(*skippedClustersIterator); - - //an unselected cluster has to be skipped, check and correct if need it - QList::iterator iterator; - for(iterator = clusterList.begin(); iterator != clusterList.end(); ++iterator) - if(!clustersIds.contains(*iterator) && !clustersIdsToSkip.contains(*iterator)) clustersIdsToSkip.append(*iterator); - qSort(clustersIdsToSkip); - view->setClusterProvider(static_cast(providers[name]),name,providerItemColors[name],true,clustersIds, - &displayGroupsClusterFile,&channelsSpikeGroups,peakSampleIndex - 1,nbSamples - peakSampleIndex,clustersIdsToSkip); - } + LoadSessionClusterFiles(loadedClusterFiles, selectedClusters, skippedClusters, view ); //Event files - for(providerIterator = loadedEventFiles.begin(); providerIterator != loadedEventFiles.end(); ++providerIterator){ - QString name = *providerIterator; - QString fileURL = providerUrls[name]; - QList eventsIds; - QList eventsIdsToSkip; - QList ids = selectedEvents[fileURL]; - QList skippedIds = skippedEvents[fileURL]; - QMap eventMap = static_cast(providers[name])->eventIdDescriptionMap(); - //only keep the event ids which are still present - QMap loadedItems = loadedEventItems[name]; - ItemColors* eventColors = providerItemColors[name]; - QList::iterator shownEventsIterator; - for(shownEventsIterator = ids.begin(); shownEventsIterator != ids.end(); ++shownEventsIterator){ - EventDescription description = EventDescription(eventColors->itemLabelById(*shownEventsIterator)); - if(eventMap.contains(*shownEventsIterator) && loadedItems.contains(description) && loadedItems[description] == *shownEventsIterator) - eventsIds.append(*shownEventsIterator); - } - QList::iterator skippedEventsIterator; - for(skippedEventsIterator = skippedIds.begin(); skippedEventsIterator != skippedIds.end(); ++skippedEventsIterator){ - EventDescription description = EventDescription(eventColors->itemLabelById(*skippedEventsIterator)); - if(eventMap.contains(*skippedEventsIterator) && loadedItems.contains(description) && loadedItems[description] == *skippedEventsIterator) - eventsIdsToSkip.append(*skippedEventsIterator); - } - - //an unselected event has to be skipped, check and correct if need it - QMap::iterator iterator; - for(iterator = eventMap.begin(); iterator != eventMap.end(); ++iterator) - if(!eventsIds.contains(iterator.key()) && !eventsIdsToSkip.contains(iterator.key())) eventsIdsToSkip.append(iterator.key()); - qSort(eventsIdsToSkip); - - view->setEventProvider(static_cast(providers[name]),name,providerItemColors[name],true,eventsIds,eventsIdsToSkip); - } + loadSessionEventFiles(loadedEventFiles, selectedEvents, skippedEvents,loadedEventItems, view); //Position file - if(!loadedPositionFile.isEmpty()){ - if(isAPositionView){ - if(rotation != 90 && rotation != 270) - view->addPositionView(static_cast(providers[loadedPositionFile]),transformedBackground, dynamic_cast(parent)->getBackgroundColor(), - startTime,duration,videoWidth,videoHeight,showEventsInPositionView); - - //If there is a rotation of 90 or 270 degree, the with and height have to be inverted. - else - view->addPositionView(static_cast(providers[loadedPositionFile]),transformedBackground, dynamic_cast(parent)->getBackgroundColor(), - startTime,duration,videoHeight,videoWidth,showEventsInPositionView); + loadSessionPositionFile(loadedPositionFile, isAPositionView, startTime, duration, showEventsInPositionView, view); - } - } } } @@ -1641,7 +2314,7 @@ QImage NeuroscopeDoc::transformBackgroundImage(bool useWhiteBackground){ //Draw the positions on the background if need it if(drawPositionsOnBackground){ //Get the PositionProvider - PositionsProvider* positionsProvider; + PositionsProvider* positionsProvider =nullptr; // previously was not initialized QHashIterator i(providers); while (i.hasNext()) { i.next(); @@ -1832,10 +2505,151 @@ void NeuroscopeDoc::setNoneEditMode(NeuroscopeView* activeView){ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const QString &clusterUrl,NeuroscopeView* activeView){ - //Check that the selected file is a cluster file - QString fileName = clusterUrl; - if(fileName.indexOf(".clu") == -1) - return INCORRECT_FILE; + if(clusterUrl.indexOf(".nwb") != -1) + return loadNWBClusterFile(clusterUrl, activeView); + + if(clusterUrl.indexOf(".nev") != -1) + return loadNevClusterFile(clusterUrl, activeView); + + if(clusterUrl.indexOf(".clu") != -1) + return loadCluClusterFile(clusterUrl, activeView); + + return INCORRECT_FILE; +} + + +int NeuroscopeDoc::setClusterColors(ItemColors* clusterColors, QList &clustersToSkip, QList &clusterList, QString name) +{ + QList::iterator it; + for(it = clusterList.begin(); it != clusterList.end(); ++it){ + QColor color = makeClusterColor(*it, false); + clusterColors->append(static_cast(*it),color); + clustersToSkip.append(static_cast(*it)); + } + providerItemColors.insert(name,clusterColors); + + return 0; +} + +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadNWBClusterFile(const QString &clusterUrl,NeuroscopeView* activeView){ + // Open file with appropiate provider + QList list = NWBClustersProvider::fromFile(clusterUrl, + this->channelLabels, + this->samplingRate, + this->tracesProvider->getTotalNbSamples(), + this->clusterPosition); + + // Set spike event sample count and peak postition + this->peakSampleIndex = 12; + this->nbSamples = 48; + + for(QList::iterator providerIterator = list.begin(); + providerIterator != list.end(); + providerIterator++) { + // Get all needed properties from cluster provider + NWBClustersProvider* clustersProvider = *providerIterator; + QString name = clustersProvider->getName(); + + + // Add cluster provider to internal structure + providers.insert(name, clustersProvider); + providerUrls.insert(name, clusterUrl); + + // Genereate cluster colors (based on color brewer) + ItemColors* clusterColors = new ItemColors(); + QList clustersToSkip; + QList clusterList = clustersProvider->clusterIdList(); + //Constructs the clusterColorList and clustersToSkip + setClusterColors(clusterColors, clustersToSkip, clusterList, name); + + + // Compute which cluster files give data for a given anatomical group + computeClusterFilesMapping(); + + // Informs the views than there is a new cluster provider. + // There should be only one view, since we only created one display. + QList clustersToShow; + for(int i = 0; i < this->viewList->count(); ++i) { + this->viewList->at(i)->setClusterProvider( + clustersProvider, + name, + clusterColors, + true, // active + clustersToShow, + &(this->displayGroupsClusterFile), + &(this->channelsSpikeGroups), + this->peakSampleIndex - 1, + this->nbSamples - peakSampleIndex, + clustersToSkip + ); + } + emit clusterFileLoaded(name); + } + + return OK; +} + + +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadNevClusterFile(const QString &clusterUrl,NeuroscopeView* activeView){ + // Open file with appropiate provider + QList list = NEVClustersProvider::fromFile(clusterUrl, + this->channelLabels, + this->samplingRate, + this->tracesProvider->getTotalNbSamples(), + this->clusterPosition); + + // Set spike event sample count and peak postition + this->peakSampleIndex = 12; + this->nbSamples = 48; + + for(QList::iterator providerIterator = list.begin(); + providerIterator != list.end(); + providerIterator++) { + // Get all needed properties from cluster provider + NEVClustersProvider* clustersProvider = *providerIterator; + QString name = clustersProvider->getName(); + + + // Add cluster provider to internal structure + providers.insert(name, clustersProvider); + providerUrls.insert(name, clusterUrl); + + // Genereate cluster colors (based on color brewer) + ItemColors* clusterColors = new ItemColors(); + QList clustersToSkip; + QList clusterList = clustersProvider->clusterIdList(); + //Constructs the clusterColorList and clustersToSkip + setClusterColors(clusterColors, clustersToSkip, clusterList, name); + + + // Compute which cluster files give data for a given anatomical group + computeClusterFilesMapping(); + + // Informs the views than there is a new cluster provider. + // There should be only one view, since we only created one display. + QList clustersToShow; + for(int i = 0; i < this->viewList->count(); ++i) { + this->viewList->at(i)->setClusterProvider( + clustersProvider, + name, + clusterColors, + true, // active + clustersToShow, + &(this->displayGroupsClusterFile), + &(this->channelsSpikeGroups), + this->peakSampleIndex - 1, + this->nbSamples - peakSampleIndex, + clustersToSkip + ); + } + emit clusterFileLoaded(name); + } + + return OK; +} + +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadCluClusterFile(const QString &clusterUrl,NeuroscopeView* activeView){ + // Open file with appropiate provider ClustersProvider* clustersProvider = new ClustersProvider(clusterUrl,datSamplingRate,samplingRate,tracesProvider->getTotalNbSamples(),clusterPosition); QString name = clustersProvider->getName(); @@ -1877,17 +2691,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const QList clustersToSkip; //Constructs the clusterColorList and clustersToSkip QList clusterList = clustersProvider->clusterIdList(); - QList::iterator it; - for(it = clusterList.begin(); it != clusterList.end(); ++it){ - QColor color; - if(*it == 1) color.setHsv(0,0,220);//Cluster 1 is always gray - else if(*it == 0) color.setHsv(0,255,255);//Cluster 0 is always red - else color.setHsv(static_cast(fmod(static_cast(*it)*7,36))*10,255,255); - clusterColors->append(static_cast(*it),color); - clustersToSkip.append(static_cast(*it)); - } - - providerItemColors.insert(name,clusterColors); + setClusterColors(clusterColors, clustersToSkip, clusterList, name); if(displayGroupsClusterFile.isEmpty()) @@ -1902,16 +2706,18 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const else view->setClusterProvider(clustersProvider,name,clusterColors,true,clustersToShow,&displayGroupsClusterFile,&channelsSpikeGroups,peakSampleIndex - 1,nbSamples - peakSampleIndex,clustersToSkip); } + emit clusterFileLoaded(name); + return OK; } -NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const QString &clusterUrl, QMap& itemColors, const QDateTime &lastModified, bool firstFile){ +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFileForSession(const QString &clusterUrl, QMap& itemColors, const QDateTime &lastModified, bool firstFile){ //Check that the selected file is a cluster file (should always be the case as the file has //already be loaded once). QString fileName = clusterUrl; if(fileName.indexOf(".clu") == -1){ QApplication::restoreOverrideCursor(); - QMessageBox::critical(0, tr("Error!"),tr("The requested cluster file %1 has an incorrect name, it has to be of the form baseName.n.clu or baseName.clu.n, with n a number identifier." + QMessageBox::critical(nullptr /*0*/, tr("Error!"),tr("The requested cluster file %1 has an incorrect name, it has to be of the form baseName.n.clu or baseName.clu.n, with n a number identifier." "Therefore will not be loaded.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_FILE; @@ -1923,7 +2729,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const if(!fileInfo.exists()){ QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The file %1 does not exist anymore.").arg(clusterUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The file %1 does not exist anymore.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return OPEN_ERROR; } @@ -1940,7 +2746,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const if(name.contains(QRegExp("\\D")) != 0){ delete clustersProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical(0, tr("Error!"),tr("The requested cluster file %1 has an incorrect name, it has to be of the form baseName.n.clu or baseName.clu.n, with n a number identifier. Therefore will not be loaded.").arg(clusterUrl)); + QMessageBox::critical(nullptr /*0*/, tr("Error!"),tr("The requested cluster file %1 has an incorrect name, it has to be of the form baseName.n.clu or baseName.clu.n, with n a number identifier. Therefore will not be loaded.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_FILE; } @@ -1949,28 +2755,28 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const if(returnStatus == ClustersProvider::OPEN_ERROR){ delete clustersProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("Could not load the file %1").arg(clusterUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("Could not load the file %1").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return OPEN_ERROR; } else if(returnStatus == ClustersProvider::MISSING_FILE){ delete clustersProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("There is no time file (.res) corresponding to the requested file %1" + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("There is no time file (.res) corresponding to the requested file %1" ", therefore this file will not be loaded.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return MISSING_FILE; } else if(returnStatus == ClustersProvider::COUNT_ERROR) { delete clustersProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The number of spikes of the requested file %1 could not be determined." + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The number of spikes of the requested file %1 could not be determined." " Therefore this file will not be loaded.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return CREATION_ERROR; } else if(returnStatus == ClustersProvider::INCORRECT_CONTENT) { delete clustersProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The number of spikes read in the requested file %1 or the corresponding time file (.res) does not correspond to number of spikes computed." + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The number of spikes read in the requested file %1 or the corresponding time file (.res) does not correspond to number of spikes computed." " Therefore this file will not be loaded.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_CONTENT; @@ -1990,13 +2796,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const clusterColors->append(static_cast(*it),itemColors[QString::number(*it)]); } else { modified = true; - QColor color; - if(*it == 1) - color.setHsv(0,0,220);//Cluster 1 is always gray - else if(*it == 0) - color.setHsv(0,255,255);//Cluster 0 is always red - else - color.setHsv(static_cast(fmod(static_cast(*it)*7,36))*10,255,255); + QColor color = makeClusterColor(*it, false); clusterColors->append(static_cast(*it),color); } } @@ -2005,11 +2805,13 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadClusterFile(const if(modified == true){ QApplication::restoreOverrideCursor(); - QMessageBox::information(0, tr("Error!"),tr("The requested file %1 has been modified since the last session," + QMessageBox::information(nullptr /*0*/, tr("Error!"),tr("The requested file %1 has been modified since the last session," " therefore some session information may be lost.").arg(clusterUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } + // TODO: Use signals for this. + if(firstFile) { static_cast(parent)->createClusterPalette(name); } else { @@ -2057,14 +2859,28 @@ void NeuroscopeDoc::setClusterPosition(int position){ } } -NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QString &eventUrl,NeuroscopeView*activeView){ +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QString &eventUrl,NeuroscopeView*activeView, int iNWBIndex){ //Check that the selected file is a event file QString fileName = eventUrl; - if(fileName.indexOf(".evt") == -1) + EventsProvider* eventsProvider(nullptr /*NULL*/); + + if(fileName.indexOf(".nwb") != -1) { + eventsProvider = new NWBEventsProvider(eventUrl, eventPosition, iNWBIndex); + } else if(fileName.indexOf(".nev") != -1) { + eventsProvider = new NEVEventsProvider(eventUrl, eventPosition); + } else if(fileName.indexOf(".evt") != -1){ + eventsProvider = new EventsProvider(eventUrl, samplingRate, eventPosition); + } else { return INCORRECT_FILE; + } - EventsProvider* eventsProvider = new EventsProvider(eventUrl,samplingRate,eventPosition); QString name = eventsProvider->getName(); + // NWB hack to have separate event names for the dictionary + if (iNWBIndex > 0) + { + // 0..9 at best. More than 10 event "files" may truncate + name.replace(2, 1, QString::number(iNWBIndex)); + } //The name should contains 3 characters with at least one none digit character. if(name.length() != 3 || name.contains(QRegExp("\\d{3}"))){ @@ -2094,7 +2910,12 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS lastLoadedProvider = name; lastEventProviderGridX = eventsProvider->getDescriptionLength(); providers.insert(name,eventsProvider); - providerUrls.insert(name,eventUrl); + + QString NWB_Hack = eventUrl; + if (iNWBIndex >0) + NWB_Hack += QString::number(iNWBIndex); + providerUrls.insert(name, NWB_Hack /*eventUrl*/); + ItemColors* eventColors = new ItemColors(); QList eventsToSkip; @@ -2103,8 +2924,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS QMap eventMap = eventsProvider->eventDescriptionIdMap(); QMap::Iterator it; for(it = eventMap.begin(); it != eventMap.end(); ++it){ - QColor color; - color.setHsv(static_cast(fmod(static_cast(it.value())*7,36))*10,255,255); + QColor color = makeClusterColor(it.value(), true); eventColors->append(static_cast(it.value()),static_cast(it.key()),color); eventsToSkip.append(static_cast(it.value())); } @@ -2124,16 +2944,18 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS else view->setEventProvider(eventsProvider,name,eventColors,true,eventsToShow,eventsToSkip); } + emit eventFileLoaded(name); + return OK; } -NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QString &eventUrl,QMap& itemColors, const QDateTime& lastModified,bool firstFile){ +NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFileForSession(const QString &eventUrl,QMap& itemColors, const QDateTime& lastModified,bool firstFile){ //Check that the selected file is a event file (should always be the case as the file has //already be loaded once). QString fileName = eventUrl; if(fileName.indexOf(".evt") == -1){ QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The requested event file %1 has an incorrect name, it has to be of the form baseName.id.evt or baseName.evt.id (with id a 3 character identifier). Therefore it will not be loaded.").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The requested event file %1 has an incorrect name, it has to be of the form baseName.id.evt or baseName.evt.id (with id a 3 character identifier). Therefore it will not be loaded.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_FILE; } @@ -2143,7 +2965,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS if(!fileInfo.exists()){ QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The file %1 does not exist anymore.").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The file %1 does not exist anymore.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return OPEN_ERROR; } @@ -2158,7 +2980,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS if(name.length() != 3 || name.contains(QRegExp("\\d{3}"))){ delete eventsProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The requested event file %1 has an incorrect name, it has to be of the form baseName.id.evt or baseName.evt.id (with id a 3 character identifier). Therefore it will not be loaded.").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The requested event file %1 has an incorrect name, it has to be of the form baseName.id.evt or baseName.evt.id (with id a 3 character identifier). Therefore it will not be loaded.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_FILE; } @@ -2168,21 +2990,21 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS if(returnStatus == EventsProvider::OPEN_ERROR){ delete eventsProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("Could not load the file %1").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("Could not load the file %1").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return OPEN_ERROR; } else if(returnStatus == EventsProvider::COUNT_ERROR){ delete eventsProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The number of events of the requested file %1 could not be determined. Therefore this file will not be loaded.").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The number of events of the requested file %1 could not be determined. Therefore this file will not be loaded.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return CREATION_ERROR; } else if(returnStatus == EventsProvider::INCORRECT_CONTENT){ delete eventsProvider; QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The content of the requested file %1 is incorrect. Therefore this file will not be loaded.").arg(eventUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The content of the requested file %1 is incorrect. Therefore this file will not be loaded.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return INCORRECT_CONTENT; } @@ -2205,8 +3027,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS } else{ modified = true; - QColor color; - color.setHsv(static_cast(fmod(static_cast(it.value())*7,36))*10,255,255); + QColor color = makeClusterColor(it.value(), true); eventColors->append(static_cast(it.value()),static_cast(it.key()),color); } } @@ -2215,7 +3036,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS if(modified == true){ QApplication::restoreOverrideCursor(); - QMessageBox::information(0, tr("Error!"),tr("The requested file %1 has been modified since the last session," + QMessageBox::information(nullptr /*0*/, tr("Error!"),tr("The requested file %1 has been modified since the last session," " therefore some session information may be lost.").arg(eventUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } @@ -2224,6 +3045,8 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadEventFile(const QS connect(eventsProvider, SIGNAL(newEventDescriptionCreated(QString,QMap,QMap,QString)),this, SLOT(slotNewEventDescriptionCreated(QString,QMap,QMap,QString))); connect(eventsProvider, SIGNAL(eventDescriptionRemoved(QString,QMap,QMap,int,QString)),this, SLOT(slotEventDescriptionRemoved(QString,QMap,QMap,int,QString))); + // TODO: Use signals for this. + if(firstFile) dynamic_cast(parent)->createEventPalette(name); else dynamic_cast(parent)->addEventFile(name); @@ -2286,7 +3109,7 @@ void NeuroscopeDoc::eventModified(const QString& providerName,int selectedEventI NeuroscopeView* view = viewList->at(i); if(view != activeView) - view->updateEvents(providerName,selectedEventId,time,newTime,false); + view->updateEvents(providerName,selectedEventId,static_cast(time),static_cast(newTime),false); } //Prepare the undo/redo mechanism @@ -2311,8 +3134,8 @@ void NeuroscopeDoc::eventRemoved(const QString& providerName,int selectedEventId //Informs the views than an event has been removed. for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->updateEventsAfterRemoval(providerName,selectedEventId,time,false); - else view->updateEventsAfterRemoval(providerName,selectedEventId,time,true); + if(view != activeView) view->updateEventsAfterRemoval(providerName,selectedEventId,static_cast(time),false); + else view->updateEventsAfterRemoval(providerName,selectedEventId,static_cast(time),true); } //Prepare the undo/redo mechanism @@ -2347,9 +3170,9 @@ void NeuroscopeDoc::eventAdded(const QString &providerName,const QString &addEve NeuroscopeView* view = viewList->at(i); if(view != activeView) - view->updateEventsAfterAddition(providerName,addedEventId,time,false); + view->updateEventsAfterAddition(providerName,addedEventId,static_cast(time),false); else - view->updateEventsAfterAddition(providerName,addedEventId,time,true); + view->updateEventsAfterAddition(providerName,addedEventId,static_cast(time),true); } } @@ -2370,19 +3193,19 @@ void NeuroscopeDoc::eventAdded(const QString &providerName,const QString &addEve } void NeuroscopeDoc::undo(NeuroscopeView* activeView){ - float time = modifiedEventTime; + double time = modifiedEventTime; modifiedEventTime = undoRedoEventTime; undoRedoEventTime = time; static_cast(providers[undoRedoProviderName])->undo(); newEventDescriptionCreated = false; - if(undoRedoEventTime != -1){ + if(!bAreNearlyEqual(undoRedoEventTime, -1)){ //Informs the views than an event has been modified. for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,modifiedEventTime,false); - else view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,modifiedEventTime,true); + if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),static_cast(modifiedEventTime),false); + else view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),static_cast(modifiedEventTime),true); } } else{ @@ -2390,34 +3213,34 @@ void NeuroscopeDoc::undo(NeuroscopeView* activeView){ for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); if(view != activeView) - view->updateEvents(undoRedoProviderName,undoRedoEventId,modifiedEventTime,false); + view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(modifiedEventTime),false); else - view->updateEvents(undoRedoProviderName,undoRedoEventId,modifiedEventTime,true); + view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(modifiedEventTime),true); } } } void NeuroscopeDoc::redo(NeuroscopeView* activeView){ - float time = modifiedEventTime; + double time = modifiedEventTime; modifiedEventTime = undoRedoEventTime; undoRedoEventTime = time; static_cast(providers[undoRedoProviderName])->redo(); newEventDescriptionCreated = false; - if(modifiedEventTime != -1){ + if( !bAreNearlyEqual(modifiedEventTime, -1)){ //Informs the views than an event has been modified. for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,modifiedEventTime,false); - else view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,modifiedEventTime,true); + if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),static_cast(modifiedEventTime),false); + else view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),static_cast(modifiedEventTime),true); } } else{ //Informs the views than an event has been added back (event previously removed) or removed (event previously added). for(int i = 0; icount(); ++i) { NeuroscopeView* view = viewList->at(i); - if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,false); - else view->updateEvents(undoRedoProviderName,undoRedoEventId,undoRedoEventTime,true); + if(view != activeView) view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),false); + else view->updateEvents(undoRedoProviderName,undoRedoEventId,static_cast(undoRedoEventTime),true); } } } @@ -2460,7 +3283,9 @@ void NeuroscopeDoc::slotNewEventDescriptionCreated(const QString &providerName,Q if(eventDescriptionAdded == removedDescription.first){ color = QColor(removedDescription.second); } - else color.setHsv(static_cast(fmod(static_cast(it.value())*7,36))*10,255,255); + else + color = makeClusterColor(it.value(), true); + eventColors->append(static_cast(it.value()),static_cast(it.key()),color); } } @@ -2558,7 +3383,6 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::createEventFile(const return ALREADY_OPENED; } - lastLoadedProvider = name; eventsProvider->initializeEmptyProvider(); lastEventProviderGridX = eventsProvider->getDescriptionLength(); providers.insert(name,eventsProvider); @@ -2646,7 +3470,7 @@ NeuroscopeDoc::OpenSaveCreateReturnMessage NeuroscopeDoc::loadPositionFile(const if(!fileInfo.exists()){ QApplication::restoreOverrideCursor(); - QMessageBox::critical (0, tr("Error!"),tr("The file %1 does not exist anymore.").arg(fileUrl)); + QMessageBox::critical (nullptr /*0*/, tr("Error!"),tr("The file %1 does not exist anymore.").arg(fileUrl)); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); return OPEN_ERROR; } @@ -2791,3 +3615,4 @@ QImage NeuroscopeDoc::getWhiteTrajectoryBackground() { return QImage(); return transformBackgroundImage(true); } + diff --git a/src/neuroscopedoc.h b/src/neuroscopedoc.h index 1f51e0a..1fecf87 100644 --- a/src/neuroscopedoc.h +++ b/src/neuroscopedoc.h @@ -14,6 +14,7 @@ * (at your option) any later version. * * * ***************************************************************************/ +#include "sessionInformation.h" #ifndef NEUROSCOPEDOC_H #define NEUROSCOPEDOC_H @@ -27,7 +28,7 @@ #include #include - +#include @@ -36,6 +37,11 @@ #include "dataprovider.h" #include "eventsprovider.h" +#ifdef WITH_CEREBUS + #include "cerebustraceprovider.h" // For SamplingGroup +#endif + +#include "neuroscopexmlreader.h" // forward declaration of the Neuroscope classes class NeuroscopeView; @@ -114,6 +120,13 @@ class NeuroscopeDoc : public QObject */ int openDocument(const QString& url); +#ifdef WITH_CEREBUS + /** Open network stream. + * @return true on sucess, false otherwise. + */ + bool openStream(CerebusTracesProvider::SamplingGroup group); +#endif + /**Saves the current session: displays, spike, cluster, event files opened and selected clusters and events. * It also saves the relevant changes in the parameter files (creating one if there is none). @return an OpenSaveCreateReturnMessage enum giving the saving status. @@ -229,15 +242,6 @@ class NeuroscopeDoc : public QObject */ int getInitialOffset()const{return initialOffset;} - /**Gets the acquisition system gains. - * @return current acquisition gain. - */ - int getAcquisitionGain()const{return acquisitionGain;} - - /**Gets the current gain based on the screen gain and the acquisition system gain. - * @return current gain. - */ - int getGain()const{return gain;} /**Gets the voltage range of the acquisition system in volts for the current document. * @return current voltage range. @@ -283,12 +287,6 @@ class NeuroscopeDoc : public QObject * @param screenGainDefault default screen gain in milivolts by centimeters used to display the field potentiels. */ void setDefaultGains(int voltageRangeDefault,int amplificationDefault,float screenGainDefault){ - acquisitionGainDefault = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolutionDefault)) - / static_cast(voltageRangeDefault * 1000)) - * amplificationDefault); - - gainDefault = static_cast(0.5 + screenGainDefault * acquisitionGainDefault); this->voltageRangeDefault = voltageRangeDefault; this->amplificationDefault = amplificationDefault; this->screenGainDefault = screenGainDefault; @@ -297,41 +295,17 @@ class NeuroscopeDoc : public QObject /**Sets the voltage range of the acquisition system in volts for the current document. * @param range current voltage range. */ - void setVoltageRange(int range){ - voltageRange = range; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - gain = static_cast(0.5 + screenGain * acquisitionGain); - } + void setVoltageRange(int range); /**Sets the amplification of the acquisition system for the current document. * @param amplification current amplification. */ - void setAmplification(int amplification){ - this->amplification = amplification; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - gain = static_cast(0.5 + screenGain * acquisitionGain); - } + void setAmplification(int amplification); /**Sets the screen gain in milivolts by centimeters used to display the field potentiels for the current document. * @param gain current screen gain. */ - void setScreenGain(float gain){ - screenGain = gain; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - gain = static_cast(0.5 + screenGain * acquisitionGain); - } + void setScreenGain(float gain); /**Sets the default initial offset for all the traces. * @param offset initial offset. @@ -378,6 +352,9 @@ class NeuroscopeDoc : public QObject */ TracesProvider& tracesDataProvider() const {return *tracesProvider;} + /** Return reference tp the mapping between channel id and label */ + QStringList* getChannelLabels() { return &channelLabels; } + /**Returns a reference on the Map given the correspondance between the channel ids and the display group ids. */ QMap* getDisplayChannelsGroups() {return &displayChannelsGroups;} @@ -458,12 +435,6 @@ class NeuroscopeDoc : public QObject this->flip = flip; drawPositionsOnBackground = positionsBackground; - acquisitionGain = static_cast(0.5 + - static_cast(pow(static_cast(2),static_cast(resolution)) - / static_cast(voltageRange * 1000)) - * amplification); - - gain = static_cast(0.5 + screenGain * acquisitionGain); } /**Returns true if the current opened file is a dat file (intial recorded file), false otherwise.*/ @@ -503,9 +474,6 @@ class NeuroscopeDoc : public QObject */ void showCalibration(bool show,NeuroscopeView* activeView); - /**Returns the name used to identified the last loaded provider. - */ - QString lastLoadedProviderName() const {return lastLoadedProvider;} /**Returns the item color list for the given provider. * @param fileName name of the file containing the data of the provider. @@ -518,6 +486,9 @@ class NeuroscopeDoc : public QObject * @return an OpenSaveCreateReturnMessage enum giving the load status. */ OpenSaveCreateReturnMessage loadClusterFile(const QString &clusterUrl,NeuroscopeView* activeView); + OpenSaveCreateReturnMessage loadNevClusterFile(const QString &clusterUrl,NeuroscopeView* activeView); + OpenSaveCreateReturnMessage loadCluClusterFile(const QString &clusterUrl,NeuroscopeView* activeView); + OpenSaveCreateReturnMessage loadNWBClusterFile(const QString &clusterUrl,NeuroscopeView* activeView); /**Loads the cluster file store in the session file and identified by @p clusterUrl. * @param clusterUrl url of the cluster file to load. @@ -526,7 +497,7 @@ class NeuroscopeDoc : public QObject * @param firstFile true if the file to load if the first one, false otherwise. * @return an OpenSaveCreateReturnMessage enum giving the load status. */ - OpenSaveCreateReturnMessage loadClusterFile(const QString &clusterUrl,QMap& itemColors,const QDateTime &lastModified,bool firstFile); + OpenSaveCreateReturnMessage loadClusterFileForSession(const QString &clusterUrl,QMap& itemColors,const QDateTime &lastModified,bool firstFile); /**Loads the position file and creates the position view in the current display. @@ -554,7 +525,7 @@ class NeuroscopeDoc : public QObject * @param activeView the view in which the change has to be immediate. * @return an OpenSaveCreateReturnMessage enum giving the load status. */ - OpenSaveCreateReturnMessage loadEventFile(const QString &eventUrl,NeuroscopeView* activeView); + OpenSaveCreateReturnMessage loadEventFile(const QString &eventUrl,NeuroscopeView* activeView, int iNWBIndex=0); /**Loads the event file store in the session file and identified by @p eventUrl. * @param eventUrl url of the event file to load. @@ -563,7 +534,7 @@ class NeuroscopeDoc : public QObject * @param firstFile true if the file to load if the first one, false otherwise. * @return an OpenSaveCreateReturnMessage enum giving the load status. */ - OpenSaveCreateReturnMessage loadEventFile(const QString &eventUrl,QMap& itemColors,const QDateTime &lastModified,bool firstFile); + OpenSaveCreateReturnMessage loadEventFileForSession(const QString &eventUrl,QMap& itemColors,const QDateTime &lastModified,bool firstFile); /**Removes the event provider corresponding to the identifier @p providerName * from the list of providers. @@ -813,6 +784,77 @@ class NeuroscopeDoc : public QObject /**Returns a reference on the the map given the of channels default offsets.*/ const QMap& getChannelDefaultOffsets()const{return channelDefaultOffsets;} +private: + // 25 March Robert H. Moore + void confirmParams(); + int openNWBDocument(const QString& url); + int openNSXDocument(const QString& url); + int treatParamSessionFile(QString fileName, QStringList fileParts, QFileInfo urlFileInfo); + void warnCommandLineProp(QString trWarning); + int readParameterFile(const QString& parFileUrl); + int readSessionFileSampling(const QString& sessionUrl); + int getNonDatSampling(); + QColor makeClusterColor(int iGroup, bool bLinear=true); + int setClusterColors(ItemColors* clusterColors, QList &clustersToSkip, QList &clusterList, QString name); + void getAssociatedFileNames(); + bool bIsNearZero(double dVal); + bool bAreNearlyEqual(double dVal1, double dVal2); + void makeOneBlueGroup(); + void setSpikePresentationInfo(bool &verticalLines, bool &raster, bool &waveforms, const QList &spikeDisplayTypes); + int openDocumentHasParam(bool sessionFileExist); + int openDocumentNoParamHasSession(); + int openDocumentNoParamNoSession(); + + void fillGains_Offsets(QList &channelGains, QList &offsets, const QList positions); + QList* getChannelsToDisplay(const QList &channelIds); + QList getSelectedChannels(const QList &selectedChannelIds); + void loadSessionFirstFileCluster(QString fileUrl, + QMap > &selectedClusters, + QMap > &skippedClusters, + QMap &itemColors, + const QDateTime &lastModified, + bool &fistClusterFile, + QStringList &loadedClusterFiles); + void loadSessionFirstFileEvents(QString fileUrl, + QMap > &selectedEvents, + QMap > &skippedEvents, + QMap &itemColors, + const QDateTime &lastModified, + bool &fistEventFile, + QStringList &loadedEventFiles, + QString sessionUrl, + QMap< QString, QMap > &loadedEventItems); + void loadSessionFirstFilePositions(QString fileUrl, + QString sessionUrl, + NeuroscopeXmlReader reader, + SessionFile &sessionFile, + QString loadedPositionFile); + void LoadSessionClusterFiles(QStringList &loadedClusterFiles, + QMap > &selectedClusters, + QMap > &skippedClusters, + NeuroscopeView* view ); + void loadSessionEventFiles(QStringList &loadedEventFiles, + QMap > &selectedEvents, + QMap > &skippedEvents, + QMap< QString, QMap > &loadedEventItems, + NeuroscopeView* view); + void loadSessionPositionFile(QString loadedPositionFile, + bool isAPositionView, + long startTime, + long duration, + bool showEventsInPositionView, + NeuroscopeView* view); + + QString qsAddAbsolutePathIfNeeded(QString qsFileName); + void BuildChannelColorList(QList &colorsList); + double dGetNonZeroVal(double dTest, double dDefault); + int iGetNonZeroVal(int iTryVal, int iDefault); + QString qsGetNonDashParameter(NeuroscopeXmlReader::fileType ft, QString qsTryVal, QString qsDefault); + QList qlGetClusterFileList(QList &anatomicalList); +#ifdef TRASH +int openDocumentNotUsed(const QString& url); +#endif + public Q_SLOTS: /**Updates the event palette and the views after the creation of a new event description. @@ -865,6 +907,16 @@ public Q_SLOTS: QList selectedChannels,QMap& skipStatus,long startTime,long duration,QString tabLabel,bool positionView,int rasterHeight, bool showEventsInPositionView); + /** Emitted when cluster file was sucessfully loaded. + * @param fileId id of the file loaded. + */ + void clusterFileLoaded(const QString& fileId); + + /** Emitted when event file was sucessfully loaded. + * @param fileId id of the file loaded. + */ + void eventFileLoaded(const QString& fileId); + private: /** The list of the views currently connected to the document */ QList* viewList; @@ -896,11 +948,6 @@ public Q_SLOTS: /**Initial offset for all the traces.*/ int initialOffset; - /**Gain which takes the screen gain into account.*/ - int gain; - - /**Acquisition system gain.*/ - int acquisitionGain; /**Screen gain in milivolts by centimeters used to display the field potentiels.*/ float screenGain; @@ -932,11 +979,6 @@ public Q_SLOTS: /**Default initial offset for all the traces.*/ int initialOffsetDefault; - /**Default gain.*/ - int gainDefault; - - /**Default acquisition system gain.*/ - int acquisitionGainDefault; /**Default screen gain in milivolts by centimeters used to display the field potentiels.*/ float screenGainDefault; @@ -947,6 +989,9 @@ public Q_SLOTS: /**Default amplification of the acquisition system.*/ int amplificationDefault; + /** Map of channel ids to channel labels */ + QStringList channelLabels; + /**Map given the correspondance between the channel ids and the display group ids.*/ QMap displayChannelsGroups; @@ -1142,6 +1187,10 @@ public Q_SLOTS: */ QImage transformBackgroundImage(bool useWhiteBackground = false); + + void nwbGetColors(QMap >& colorMapList, QMap& chanGroupMap, int channelNb, std::string hsFileName); + + }; #endif // NEUROSCOPEDOC_H diff --git a/src/neuroscopeview.cpp b/src/neuroscopeview.cpp index 9edd8a8..819f0e2 100644 --- a/src/neuroscopeview.cpp +++ b/src/neuroscopeview.cpp @@ -34,7 +34,7 @@ class EventData; NeuroscopeView::NeuroscopeView(NeuroscopeApp& mainWindow, const QString &label, long startTime, long duration, const QColor &backgroundColor, int wflags, QStatusBar* statusBar, QList* channelsToDisplay, bool greyScale, TracesProvider& tracesProvider, bool multiColumns, bool verticalLines, - bool raster, bool waveforms, bool labelsDisplay, int unitGain, int acquisitionGain, ChannelColors* channelColors, + bool raster, bool waveforms, bool labelsDisplay, float screenGain, ChannelColors* channelColors, QMap >* groupsChannels, QMap* channelsGroups, bool autocenterChannels, QList offsets, QList channelGains, QList selected, QMap skipStatus, int rasterHeight, const QString &backgroundImagePath, QWidget* parent, const char* name): DockArea(parent) @@ -67,10 +67,14 @@ NeuroscopeView::NeuroscopeView(NeuroscopeApp& mainWindow, const QString &label, addDockWidget(Qt::RightDockWidgetArea,mainDock); traceWidget = new TraceWidget(startTime,duration,greyScale,tracesProvider,multiColumns,verticalLines,raster, - waveforms,labelsDisplay,*shownChannels,unitGain,acquisitionGain,channelColors,groupsChannels,channelsGroups,autocenterChannels, + waveforms,labelsDisplay,*shownChannels,screenGain,channelColors,groupsChannels,channelsGroups,autocenterChannels, channelOffsets,gains,skippedChannels,rasterHeight,QImage(backgroundImagePath),mainDock,"traces",backgroundColor,statusBar,5); - /// Added by M.Zugaro to enable automatic forward paging - connect(traceWidget,SIGNAL(stopped()),this,SLOT(traceWidgetStopped())); + + // Forward paging events + connect(traceWidget, SIGNAL(pagingStarted()), + this, SLOT(traceWidgetStarted())); + connect(traceWidget, SIGNAL(pagingStopped()), + this, SLOT(traceWidgetStopped())); mainDock->setWidget(traceWidget); mainDock->setFocusPolicy(Qt::NoFocus); @@ -92,7 +96,7 @@ NeuroscopeView::NeuroscopeView(NeuroscopeApp& mainWindow, const QString &label, connect(this,SIGNAL(decreaseAllAmplitude()),traceWidget,SLOT(decreaseAllChannelsAmplitude())); connect(this,SIGNAL(increaseAmplitude(QList)),traceWidget,SLOT(increaseSelectedChannelsAmplitude(QList))); connect(this,SIGNAL(decreaseAmplitude(QList)),traceWidget,SLOT(decreaseSelectedChannelsAmplitude(QList))); - connect(this,SIGNAL(updateGains(int,int)),traceWidget,SLOT(setGains(int,int))); + connect(this,SIGNAL(updateScreenGain(float)),traceWidget,SLOT(setGain(float))); connect(this,SIGNAL(updateDrawing()),traceWidget, SLOT(updateDrawing())); connect(this,SIGNAL(groupsHaveBeenModified(bool)),traceWidget, SLOT(groupsModified(bool))); connect(this,SIGNAL(channelsToBeSelected(QList)),traceWidget,SLOT(selectChannels(QList))); diff --git a/src/neuroscopeview.h b/src/neuroscopeview.h index 284806c..5b024e8 100644 --- a/src/neuroscopeview.h +++ b/src/neuroscopeview.h @@ -76,8 +76,7 @@ class NeuroscopeView : public DockArea * @param raster true if a raster is drawn to show the clusters, false otherwise * @param waveforms true if waveforms are drawn on top of the traces, false otherwise. * @param labelsDisplay true if labels are drawn next to the traces, false otherwise. - * @param unitGain initial gain use to draw the traces in the TraceView. - * @param acquisitionGain acquisition gain. + * @param screenGain initial screen gain used to draw the traces in the TraceView. * @param channelColors a pointer on the list of colors for the channels. * @param groupsChannels a pointer on the map given the list of channels for each group. * @param channelsGroups a pointer on the map given to which group each channel belongs. @@ -93,7 +92,7 @@ class NeuroscopeView : public DockArea */ NeuroscopeView(NeuroscopeApp& mainWindow,const QString& label,long startTime,long duration,const QColor& backgroundColor,int wflags,QStatusBar * statusBar,QList* channelsToDisplay,bool greyScale, TracesProvider& tracesProvider,bool multiColumns,bool verticalLines, - bool raster,bool waveforms,bool labelsDisplay,int unitGain,int acquisitionGain,ChannelColors* channelColors, + bool raster,bool waveforms,bool labelsDisplay, float screenGain, ChannelColors* channelColors, QMap >* groupsChannels,QMap* channelsGroups, bool autocenterChannels, QList offsets,QList channelGains,QList selected,QMap skipStatus,int rasterHeight,const QString& backgroundImagePath, QWidget *parent = 0, const char *name=0); @@ -107,12 +106,11 @@ class NeuroscopeView : public DockArea */ void print(QPrinter* pPrinter,const QString& filePath,bool whiteBackground); - /**Sets the unit gain and the acquisition system gain. - * @param gain initial gain use to draw the traces in the TraceView. - * @param acquisitionGain acquisition gain. + /**Sets the unit screen gain. + * @param screenGain initial gain use to draw the traces in the TraceView. */ - void setGains(int gain,int acquisitionGain ){ - emit updateGains(gain,acquisitionGain); + void setGains(float screenGain){ + emit updateScreenGain(screenGain); emit drawTraces(); } @@ -609,13 +607,19 @@ class NeuroscopeView : public DockArea private Q_SLOTS: - /// Added by M.Zugaro to enable automatic forward paging - void traceWidgetStopped() { emit stopped(); } + /** Forward paging started event info */ + void traceWidgetStarted() { emit pagingStarted(); } + + /** Forward paging stopped event info */ + void traceWidgetStopped() { emit pagingStopped(); } Q_SIGNALS: - /// Added by M.Zugaro to enable automatic forward paging - void stopped(); + /** Emitted when paging in started */ + void pagingStarted(); + + /** Emitted when paging in stopped */ + void pagingStopped(); public Q_SLOTS: @@ -725,7 +729,7 @@ public Q_SLOTS: void decreaseAllAmplitude(); void increaseAmplitude(const QList& selectedIds); void decreaseAmplitude(const QList& selectedIds); - void updateGains(int gain,int acquisitionGain); + void updateScreenGain(float screenGain); void updateDrawing(); void groupsHaveBeenModified(bool); void channelsToBeSelected(const QList& selectedIds); diff --git a/src/neuroscopexmlreader.cpp b/src/neuroscopexmlreader.cpp index 66699b1..118897b 100644 --- a/src/neuroscopexmlreader.cpp +++ b/src/neuroscopexmlreader.cpp @@ -70,6 +70,95 @@ void NeuroscopeXmlReader::closeFile(){ } +QString NeuroscopeXmlReader::getGenericText(QString qsTag, QString defaultTextIn)const { + qDebug() << "getGenericText()" << " " << qsTag << "\n"; + QString defaultText = defaultTextIn; + QDomNode n = documentNode.firstChild(); + if (!n.isNull()) { + while (!n.isNull()) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (!e.isNull()) { + QString tag = e.tagName(); + //qDebug() << tag << " " << qsTag << "\n"; + if (tag == qsTag) { + defaultText = e.text(); + return defaultText; + } + } + n = n.nextSibling(); + } + } + + return defaultText; +} + + +QList NeuroscopeXmlReader::getListGenericTexts(QString qsTag, QString defaultTextIn)const { + qDebug() << "getListGenericTexts()" << " " << qsTag << "\n"; + QList lstNames = QList< QString >(); + + QDomNode n = documentNode.firstChild(); + if (!n.isNull()) { + while (!n.isNull()) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (!e.isNull()) { + QString tag = e.tagName(); + //qDebug() << tag << " " << qsTag << "\n"; + if (tag == qsTag) { + lstNames.append(e.text()); + } + } + n = n.nextSibling(); + } + } + + return lstNames; +} + + + +QString NeuroscopeXmlReader::getNWBSamplingName()const { + QString SamplingName = "/processing/ecephys/LFP/lfp/starting_time"; + QDomNode n = documentNode.firstChild(); + if (!n.isNull()) { + while (!n.isNull()) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (!e.isNull()) { + QString tag = e.tagName(); + if (tag == NWB_SAMPLING_NAME) { + SamplingName = e.text(); + return SamplingName; + } + } + n = n.nextSibling(); + } + } + + return SamplingName; +} + + +QString NeuroscopeXmlReader::getNWBDataSetName()const { + QString DataSetName = "/processing/ecephys/LFP/lfp/data"; + QDomNode n = documentNode.firstChild(); + if (!n.isNull()) { + while (!n.isNull()) { + QDomElement e = n.toElement(); // try to convert the node to an element. + if (!e.isNull()) { + QString tag = e.tagName(); + if (tag == NWB_DATASET_NAME) { + DataSetName = e.text(); + return DataSetName; + } + } + n = n.nextSibling(); + } + } + + return DataSetName; +} + + int NeuroscopeXmlReader::getResolution()const{ int resolution = 0; QDomNode n = documentNode.firstChild(); @@ -301,13 +390,13 @@ int NeuroscopeXmlReader::getVoltageRange() const{ QDomNode acquisition = e.firstChild(); // try to convert the node to an element. while(!acquisition.isNull()) { QDomElement w = acquisition.toElement(); - if(!w.isNull()) { - tag = w.tagName(); - if (tag == VOLTAGE_RANGE) { - range = w.text().toInt(); - return range; + if(!w.isNull()) { + tag = w.tagName(); + if (tag == VOLTAGE_RANGE) { + range = w.text().toInt(); + return range; + } } - } acquisition = acquisition.nextSibling(); } } @@ -360,13 +449,13 @@ int NeuroscopeXmlReader::getAmplification() const{ QDomNode acquisition = e.firstChild(); // try to convert the node to an element. while(!acquisition.isNull()) { QDomElement w = acquisition.toElement(); - if(!w.isNull()) { - tag = w.tagName(); - if (tag == AMPLIFICATION) { - amplification = w.text().toInt(); - return amplification; + if(!w.isNull()) { + tag = w.tagName(); + if (tag == AMPLIFICATION) { + amplification = w.text().toInt(); + return amplification; + } } - } acquisition = acquisition.nextSibling(); } } @@ -419,13 +508,13 @@ int NeuroscopeXmlReader::getOffset()const{ QDomNode acquisition = e.firstChild(); // try to convert the node to an element. while(!acquisition.isNull()) { QDomElement w = acquisition.toElement(); - if(!w.isNull()) { - tag = w.tagName(); - if (tag == OFFSET) { - offset = w.text().toInt(); - return offset; + if(!w.isNull()) { + tag = w.tagName(); + if (tag == OFFSET) { + offset = w.text().toInt(); + return offset; + } } - } acquisition = acquisition.nextSibling(); } } diff --git a/src/neuroscopexmlreader.h b/src/neuroscopexmlreader.h index bf9d713..420087b 100644 --- a/src/neuroscopexmlreader.h +++ b/src/neuroscopexmlreader.h @@ -223,6 +223,14 @@ class NeuroscopeXmlReader { */ NeuroscopeXmlReader::fileType getType()const{return type;} + + /** Get the Neurodata Without Borders File Locations */ + /** Added by Robert H. Moore (RHM) */ + QString getNWBSamplingName() const; + QString getNWBDataSetName() const; + QString getGenericText(QString qsTag, QString defaultTextIn) const; + QList getListGenericTexts(QString qsTag, QString defaultTextIn)const; + private: fileType type; QString readVersion; diff --git a/src/nevclustersprovider.cpp b/src/nevclustersprovider.cpp new file mode 100644 index 0000000..b541072 --- /dev/null +++ b/src/nevclustersprovider.cpp @@ -0,0 +1,190 @@ +/*************************************************************************** + nsxtracesprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "nevclustersprovider.h" + +#include + +NEVClustersProvider::NEVClustersProvider(unsigned int channel,Array& data, + int spikeCount, + double samplingRate, + double currentSamplingRate, + dataType fileMaxTime, + int position) + : ClustersProvider(QString("nev.%1.clu").arg(channel + 1), + samplingRate, + currentSamplingRate, + fileMaxTime, + position) { + + // Override base clase settings + // TODO: Clean up base clases so this is no longer required. + this->name = QString::number(channel + 1); + this->nbSpikes = spikeCount; + this->clusters.setSize(2, spikeCount); + + // Copy data to internal structure + clusters.copySubset(data, this->nbSpikes); + + // Determine list of unique cluster + for(int i = 1; i <= this->nbSpikes; i++) { + if(!clusterIds.contains(clusters(1, i))) + clusterIds << clusters(1, i); + } + this->nbClusters = clusterIds.size(); + + //Initialize the variables + this->previousStartTime = 0; + this->previousStartIndex = 1; + this->previousEndIndex = this->nbSpikes; + this->previousEndTime = (clusters(2, this->nbSpikes) * 1000.0 / samplingRate) + 0.5; + this->fileMaxTime = this->previousEndTime; +} + + +int NEVClustersProvider::loadData() { + // Do nothing, this is all done by fromFile(...) + return OK; +} + +NEVClustersProvider::~NEVClustersProvider() { + +} + +QList NEVClustersProvider::fromFile(const QString& fileUrl, + QStringList channelLabels, + double currentSamplingRate, + dataType fileMaxTime, + int position = 25) { + QList result; + + // Try to open file + QFile clusterFile(fileUrl); + if(!clusterFile.open(QIODevice::ReadOnly)) { + return result; + } + + // Read basic header + NEVBasicHeader basicHeader; + if(!readStruct(clusterFile, basicHeader)) { + clusterFile.close(); + return result; + } + + // Extract information we need from basic header + double samplingRate = basicHeader.global_time_resolution; + long eventCount = (clusterFile.size() - basicHeader.header_size) / basicHeader.data_package_size; + + // Read extension headers and extract label mapping + QMap channelLabelsToIds; + NEVExtensionHeader extensionHeader; + for(unsigned int extension = 0; extension < basicHeader.extension_count; extension++) { + if(!readStruct(clusterFile, extensionHeader)) { + clusterFile.close(); + return result; + } + + // Extract all the label headers + if(!strncmp(extensionHeader.id, NEVNeuralLabelID, 8)) { + NEVNeuralLabelExtensionData* labelHeader = reinterpret_cast(extensionHeader.data); + channelLabelsToIds.insert(QString(labelHeader->label), labelHeader->id); + } + } + + // Find channel ids we need to extract + QList channelIds; + for(int i = 0; i < channelLabels.size(); i++) { + QMap::iterator id = channelLabelsToIds.find(channelLabels[i]); + if(id == channelLabelsToIds.end()) { + // Label could not be found in label header. + qCritical("Can not find label '%s' in nev file.", channelLabels[i].toUtf8().constData()); + return result; + } + channelIds.append(id.value()); + if((++id).key() == channelLabels[i]) { + // Only complain about duplicate labels if we are actually using them. + qCritical("Duplicate label '%s' found in nev file. Can not determine channel id correctly.", channelLabels[i].toUtf8().constData()); + return result; + } + } + + // Prepare data structure + int channelCount = channelLabels.size(); + + QList spikeCount; + // Array copy constructor is broken, so we have to use pointers. + QList*> data; + for(int i = 0; i < channelCount; i++) { + data << new Array(2, eventCount); + spikeCount << 0; + } + + // Read data packages + NEVDataHeader dataHeader; + for(long i = 0; i < eventCount; i++) { + if(!readStruct(clusterFile, dataHeader)) + return result; + + if(dataHeader.timestamp == 0xFFFFFFFF){ + qCritical("Continuation packages are not supported!"); + return result; + } + if(dataHeader.id > 0 && dataHeader.id < 2049) { + NEVSpikeDataHeader spikeData; + if(!readStruct(clusterFile, spikeData)) + return result; + + // Check if we are interested in spikes on this channel. + int index = channelIds.indexOf(dataHeader.id); + if(index != -1) { + // We are interested, so copy spike info; + spikeCount[index]++; + (*data[index])(1, spikeCount[index]) = spikeData.unit_class; + (*data[index])(2, spikeCount[index]) = dataHeader.timestamp; + } + + // Skip the rest of the data + if(!clusterFile.seek(clusterFile.pos() + basicHeader.data_package_size + - sizeof(NEVSpikeDataHeader) + - sizeof(NEVDataHeader))) { + return result; + } + } else { + // Skip event package + if(!clusterFile.seek(clusterFile.pos() + basicHeader.data_package_size + - sizeof(NEVDataHeader))) + return result; + } + } + clusterFile.close(); + + // Create provider objects + for(int i = 0; i < channelCount; i++) { + result.append(new NEVClustersProvider(i, + *data[i], + spikeCount[i], + samplingRate, + currentSamplingRate, + fileMaxTime, + position)); + } + + // Array copy constructor is broken, so we have to use pointers. + for(int i = 0; i < channelCount; i++) { + delete data[i]; + } + + return result; +} diff --git a/src/nevclustersprovider.h b/src/nevclustersprovider.h new file mode 100644 index 0000000..94e6545 --- /dev/null +++ b/src/nevclustersprovider.h @@ -0,0 +1,51 @@ +/*************************************************************************** + nevclustersprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _NEVCLUSTERSPROVIDER_H_ +#define _NEVCLUSTERSPROVIDER_H_ + +#include + +#include "blackrock.h" +#include "clustersprovider.h" + +class NEVClustersProvider : public ClustersProvider { + Q_OBJECT + +public: + static QList fromFile(const QString& file, + QStringList channelLabels, + double samplingRate, + dataType fileMaxTime, + int position); + + ~NEVClustersProvider(); + + /**Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + */ + virtual int loadData(); + +private: + NEVClustersProvider(unsigned int channel, + Array& data, + int spikeCount, + double samplingRate, + double currentSamplingRate, + dataType fileMaxTime, + int position); +}; + +#endif diff --git a/src/neveventsprovider.cpp b/src/neveventsprovider.cpp new file mode 100644 index 0000000..d16fa28 --- /dev/null +++ b/src/neveventsprovider.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + neveventsprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "neveventsprovider.h" + +#include + +NEVEventsProvider::NEVEventsProvider(const QString &fileUrl, int position) : EventsProvider(".nev.evt", 0, position), mExtensionHeaders(NULL){ + this->fileName = fileUrl; +} + +NEVEventsProvider::~NEVEventsProvider() { + if(mExtensionHeaders) + delete[] mExtensionHeaders; +} + +int NEVEventsProvider::loadData(){ + // Empty previous data + events.setSize(0,0); + timeStamps.setSize(0,0); + + // Try to open file + QFile eventFile(this->fileName); + if(!eventFile.open(QIODevice::ReadOnly)) { + return OPEN_ERROR; + } + + // Read basic header + if(!readStruct(eventFile, mBasicHeader)) { + eventFile.close(); + return INCORRECT_CONTENT; + } + + this->currentSamplingRate = mBasicHeader.global_time_resolution / 1000.0; + this->nbEvents = (eventFile.size() - mBasicHeader.header_size) / mBasicHeader.data_package_size; + + // Read extension headers + if(mExtensionHeaders) + delete[] mExtensionHeaders; + mExtensionHeaders = new NEVExtensionHeader[mBasicHeader.extension_count]; + + for(int extension = 0; extension < mBasicHeader.extension_count; extension++) { + if(!readStruct(eventFile, mExtensionHeaders[extension])) { + eventFile.close(); + delete[] mExtensionHeaders; + mExtensionHeaders = NULL; + return INCORRECT_CONTENT; + } + } + + // Extension header information should be extracted here. Right now we do not use the information. + + // Read data packages + NEVDataHeader dataHeader; + Array tempTimestamps(1, this->nbEvents); + pArray tempDescription; //pArray is missing a constructor + tempDescription.setSize(1, this->nbEvents); + long eventIndex(0L); + long eventsSkipped(0L); + while(eventIndex + eventsSkipped < this->nbEvents) { + if(!readStruct(eventFile, dataHeader)) + goto fail; + + if(dataHeader.timestamp == 0xFFFFFFFF){ + qCritical() << "Continuation packages are not supported!"; + goto fail; + } + + // Create proper label + EventDescription label; + switch(dataHeader.id) { + case NEVDigitalSerialDataID: + NEVDigitalSerialData digtalData; + if(!readStruct(eventFile, digtalData)) + goto fail; + + if(digtalData.reason & (1 << 7)) + label = QString("serial data"); + else + label = QString("digital data"); + + // Skip the padding + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVDigitalSerialData) + - sizeof(NEVDataHeader))) + goto fail; + break; + case NEVConfigurationDataID: + NEVConfigurationDataHeader configData; + if(!readStruct(eventFile, configData)) + goto fail; + + switch(configData.type) { + case 0: label = EventDescription("config change normal"); break; + case 1: label = EventDescription("config change critical"); break; + default: label = EventDescription("config change undefined"); break; + } + + // Skip the config change data + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVConfigurationDataHeader) + - sizeof(NEVDataHeader))) + goto fail; + break; + case NEVButtonDataID: + NEVButtonData buttonData; + if(!readStruct(eventFile, buttonData)) + goto fail; + + switch(buttonData.trigger) { + case 1: label = EventDescription("button press"); break; + case 2: label = EventDescription("button reset"); break; + default: label = EventDescription("button undefined"); break; + } + + // Skip the padding + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVButtonData) + - sizeof(NEVDataHeader))) + goto fail; + break; + case NEVTrackingDataID: + NEVTrackingDataHeader trackingData; + if(!readStruct(eventFile, trackingData)) + goto fail; + + label = QString("tracking (p: %1 n: %1)").arg(trackingData.parent_id).arg(trackingData.node_id); + + // Skip the tracking data points + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVTrackingDataHeader) + - sizeof(NEVDataHeader))) + goto fail; + break; + case NEVVideoSyncDataID: + NEVVideoSyncData syncData; + if(!readStruct(eventFile, syncData)) + goto fail; + + label = QString("video sync (s: %1)").arg(syncData.id); + + // Skip the padding + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVVideoSyncData) + - sizeof(NEVDataHeader))) + goto fail; + break; + case NEVCommentDataID: + label = EventDescription("comment"); + + if(!eventFile.seek(eventFile.pos() + mBasicHeader.data_package_size + - sizeof(NEVDataHeader))) + goto fail; + break; + default: + if(dataHeader.id > 0 && dataHeader.id < 2049) { + // Skip spiking events, see NEVClustersProvider + if(!eventFile.seek(eventFile.pos() + + mBasicHeader.data_package_size + - sizeof(NEVDataHeader))) { + goto fail; + } + eventsSkipped++; + continue; + } else { + qDebug() << "Unknown package id:" << dataHeader.id; + goto fail; + } + } + + // Save time and label of event + tempTimestamps[eventIndex] = dataHeader.timestamp; + tempDescription[eventIndex] = label; + eventIndex++; + + // Update event category count + long eventCount = eventDescriptionCounter.contains(label) ? eventDescriptionCounter[label]: 0; + eventDescriptionCounter.insert(label, eventCount + 1); + } + eventFile.close(); + + // Now we know how many events were skipped, we can update the object + this->nbEvents -= eventsSkipped; + + timeStamps.setSize(1, this->nbEvents); + events.setSize(1, this->nbEvents); + + timeStamps.copySubset(tempTimestamps, this->nbEvents); + events.copySubset(tempDescription, this->nbEvents); + + // Update internal struture + this->updateMappingAndDescriptionLength(); + + //Initialize the variables + previousStartTime = 0; + previousStartIndex = 1; + previousEndIndex = this->nbEvents; + previousEndTime = static_cast(floor(0.5 + this->timeStamps(1, this->nbEvents))); + fileMaxTime = previousEndTime; + + return OK; + +fail: + eventFile.close(); + delete[] mExtensionHeaders; + mExtensionHeaders = NULL; + return INCORRECT_CONTENT; +} diff --git a/src/neveventsprovider.h b/src/neveventsprovider.h new file mode 100644 index 0000000..63204a6 --- /dev/null +++ b/src/neveventsprovider.h @@ -0,0 +1,39 @@ +/*************************************************************************** + neveventsprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef _NEVEVENTSPROVIDER_H_ +#define _NEVEVENTSPROVIDER_H_ + +#include "blackrock.h" +#include "eventsprovider.h" + +class NEVEventsProvider : public EventsProvider { + Q_OBJECT + +public: + NEVEventsProvider(const QString &fileUrl, int position); + ~NEVEventsProvider(); + + /**Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + */ + virtual int loadData(); + +private: + NEVBasicHeader mBasicHeader; + NEVExtensionHeader* mExtensionHeaders; +}; + +#endif diff --git a/src/nsxtracesprovider.cpp b/src/nsxtracesprovider.cpp new file mode 100644 index 0000000..e3d8238 --- /dev/null +++ b/src/nsxtracesprovider.cpp @@ -0,0 +1,200 @@ +/*************************************************************************** + nsxtracesprovider.cpp - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "nsxtracesprovider.h" + +#include +#include + + +const int NSXTracesProvider::NSX_RESOLUTION = 16; +const int NSXTracesProvider::NSX_OFFSET = 0; + +NSXTracesProvider::NSXTracesProvider(const QString &fileName) + : TracesProvider(fileName, -1, NSX_RESOLUTION, 0, 0, 0, NSX_OFFSET), mInitialized(false), mExtensionHeaders(NULL), mDataFilePos(-1) { +} + +NSXTracesProvider::~NSXTracesProvider() { + if(mInitialized) + delete[] mExtensionHeaders; +} + +bool NSXTracesProvider::init() { + // Do not initialize twice + if(mInitialized) + return true; + + // Try to open file + QFile dataFile(this->fileName); + if(!dataFile.open(QIODevice::ReadOnly)) { + return false; + } + + // Read basic header + if(!readStruct(dataFile, mBasicHeader)) { + dataFile.close(); + return false; + } + + // Parse basic header + this->nbChannels = mBasicHeader.channel_count; + this->samplingRate = 30000.0 / mBasicHeader.sampling_period; + + // Read extension headers + mExtensionHeaders = new NSXExtensionHeader[this->nbChannels]; + + for(int channel = 0; channel < this->nbChannels; channel++) { + if(!readStruct(dataFile, mExtensionHeaders[channel])) { + delete[] mExtensionHeaders; + dataFile.close(); + return false; + } + } + + // Read data header + if(!readStruct(dataFile, mDataHeader)) { + delete[] mExtensionHeaders; + dataFile.close(); + return false; + } + + // Save beginning of data block + mDataFilePos = dataFile.pos(); + + // Close file and update recording length + dataFile.close(); + mInitialized = true; + computeRecordingLength(); + return true; +} + +long NSXTracesProvider::getNbSamples(long start, long end, long startInRecordingUnits) { + // Check if startInRecordingUnits was supplied, else compute it. + if(startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + + // The caller should have check that we do not go over the end of the file. + // The recording starts at time equals 0 and ends at length of the file minus one. + // Therefore the sample at endInRecordingUnits is never returned. + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + + return endInRecordingUnits - startInRecordingUnits; +} + +void NSXTracesProvider::retrieveData(long start, long end, QObject* initiator, long startInRecordingUnits) { + Array data; + + if(!mInitialized) { + qDebug() << "no init!"; + emit dataReady(data,initiator); + return; + } + + QFile dataFile(this->fileName); + if(!dataFile.open(QIODevice::ReadOnly)) { + qDebug() << "cant open!"; + emit dataReady(data,initiator); + return; + } + + // Check if startInRecordingUnits was supplied, else compute it. + if(startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + + long fileOffset = startInRecordingUnits * this->nbChannels * sizeof(int16_t); + + // The caller should have check that we do not go over the end of the file. + // The recording starts at time equals 0 and ends at length of the file minus one. + // Therefore the sample at endInRecordingUnits is never returned. + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + + long lengthInRecordingUnits = endInRecordingUnits - startInRecordingUnits; + + // Jump to position to read (skipping headers) + if(!dataFile.seek(mDataFilePos + fileOffset)) { + qDebug() << "cant skip!"; + dataFile.close(); + emit dataReady(data,initiator); + return; + } + + // Allocate buffer + Array buffer(lengthInRecordingUnits, this->nbChannels); + + // Read data + qint64 bytesToRead = lengthInRecordingUnits * this->nbChannels * sizeof(int16_t); + qint64 bytesRead = dataFile.read(reinterpret_cast(&buffer[0]), bytesToRead); + + if(bytesToRead != bytesRead) { + qDebug() << "cant read " << bytesRead << " not " << bytesToRead; + dataFile.close(); + emit dataReady(data,initiator); + return; + } + dataFile.close(); + + // Copy data to dataType array after translating them to uV + data.setSize(lengthInRecordingUnits, this->nbChannels); + for(int channel = 0; channel < this->nbChannels; channel++) { + // Determine unit data is saved in + int unit_correction = 0; + if(!strncmp(mExtensionHeaders[channel].unit, "uV", 16)) { + unit_correction = 1; + } else if(!strncmp(mExtensionHeaders[channel].unit, "mV", 16)) { + unit_correction = 1000; + } else { + qDebug() << "unknown unit: " << mExtensionHeaders[channel].unit; + dataFile.close(); + data.setSize(0, 0); + emit dataReady(data, initiator); + return; + } + + // Get all the values needed to translate measurement unit to uV + int min_digital = mExtensionHeaders[channel].min_digital_value; + int range_digital = mExtensionHeaders[channel].max_digital_value - min_digital; + int min_analog = mExtensionHeaders[channel].min_analog_value; + int range_analog = mExtensionHeaders[channel].max_analog_value - min_analog; + + // Convert data + for(int i = 1; i <= lengthInRecordingUnits; i++) { + data(i, channel + 1) = static_cast((((static_cast(buffer(i, channel + 1)) - min_digital) / range_digital) * range_analog + min_analog) * unit_correction); + } + + } + + //Send the information to the receiver. + emit dataReady(data, initiator); +} + +void NSXTracesProvider::computeRecordingLength(){ + // We don't know the length if not initialized + if(!mInitialized) { + this->length = -1; + return; + } + + this->length = (1000.0 * mDataHeader.length) / this->samplingRate; +} + +QStringList NSXTracesProvider::getLabels() { + QStringList labels; + + for(int i = 0; i < this->nbChannels; i++) { + labels << QString(mExtensionHeaders[i].label); + } + + return labels; +} diff --git a/src/nsxtracesprovider.h b/src/nsxtracesprovider.h new file mode 100644 index 0000000..aa94f0c --- /dev/null +++ b/src/nsxtracesprovider.h @@ -0,0 +1,127 @@ +/*************************************************************************** + nsxtracesprovider.h - description + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef NSXTRACESPROVIDER_H +#define NSXTRACESPROVIDER_H + +//include files for the application +#include "tracesprovider.h" +#include "array.h" +#include "types.h" +#include "blackrock.h" + +// include files for QT +#include +#include + +/** Class providing the row recorded data (contained in a .nsx file). + * @author Florian Franzen + */ + +class NSXTracesProvider : public TracesProvider { + Q_OBJECT +public: + + /**Constructor. + * @param fileUrl the url of the file containing the data provided by this class. + */ + NSXTracesProvider(const QString &fileUrl); + virtual ~NSXTracesProvider(); + + /**Sets the number of channels corresponding to the file identified by fileUrl. + * @param nb the number of channels. + */ + virtual void setNbChannels(int nb){ + qDebug() << "NSX file used. Ignoring setNbChannels(" << nb << ")"; + } + + /**Sets the resolution used to record the data contained in the file identified by fileUrl. + * @param res resolution. + */ + virtual void setResolution(int res){ + qDebug() << "NSX file used. Ignoring setResolution(" << res << ")"; + } + + /**Sets the sampling rate used to record the data contained in the file identified by fileUrl. + * @param rate the sampling rate. + */ + virtual void setSamplingRate(double rate){ + qDebug() << "NSX file used. Ignoring setSamplingRate(" << rate << ")"; + } + + /**Sets the voltage range used to record the data contained in the file identified by fileUrl. + * @param range the voltage range. + */ + virtual void setVoltageRange(int range){ + qDebug() << "NSX file used. Ignoring setVoltageRange(" << range << ")"; + } + + /**Sets the amplification used to record the data contained in the file identified by fileUrl. + * @param value the amplification. + */ + virtual void setAmplification(int value){ + qDebug() << "NSX file used. Ignoring setAmplification(" << value << ")"; + } + + /** Initializes the object by reading the nsx files header. + */ + bool init(); + + /**Computes the number of samples between @p start and @p end. + * @param start begining of the time frame from which the data have been retrieved, given in milisecond. + * @param end end of the time frame from which to retrieve the data, given in milisecond. + * @return number of samples in the given time frame. + * @param startTimeInRecordingUnits begining of the time frame from which the data have been retrieved, given in recording units. + */ + virtual long getNbSamples(long start, long end, long startInRecordingUnits); + + /** Return the labels of each channel as read from nsx file. */ + virtual QStringList getLabels(); + +Q_SIGNALS: + /**Signals that the data have been retrieved. + * @param data array of data in uV (number of channels X number of samples). + * @param initiator instance requesting the data. + */ + void dataReady(Array& data,QObject* initiator); + +private: + static const int NSX_RESOLUTION; + static const int NSX_OFFSET; + + // Headers that are parsed from the file to open + NSXBasicHeader mBasicHeader; + NSXExtensionHeader* mExtensionHeaders; + NSXDataHeader mDataHeader; + + // Start of first sample in first data package. + long mDataFilePos; + bool mInitialized; + + //Functions + + /**Retrieves the traces included in the time frame given by @p startTime and @p endTime. + * @param startTime begining of the time frame from which to retrieve the data, given in milisecond. + * @param endTime end of the time frame from which to retrieve the data, given in milisecond. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void retrieveData(long startTime, long endTime, QObject* initiator, long startTimeInRecordingUnits); + + /**Computes the total length of the document in miliseconds.*/ + virtual void computeRecordingLength(); +}; + +#endif diff --git a/src/nwbclustersprovider.cpp b/src/nwbclustersprovider.cpp new file mode 100644 index 0000000..771365f --- /dev/null +++ b/src/nwbclustersprovider.cpp @@ -0,0 +1,106 @@ +/*************************************************************************** + nwbclusterprovider.h - description + ------------------- + Written by Robert H. Moore (RHM), + but adapted from: + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include + +#include "nwbclustersprovider.h" +#include "nwbreader.h" + +NWBClustersProvider::NWBClustersProvider(unsigned int channel, + Array& data, + int spikeCount, + double samplingRate, + double currentSamplingRate, + dataType fileMaxTime, + int position) + : ClustersProvider(QString("nwb.%1.clu").arg(channel + 1), + samplingRate, + currentSamplingRate, + fileMaxTime, + position) { + + // Override base clase settings + // TODO: Clean up base clases so this is no longer required. + this->name = QString::number(channel + 1); + this->nbSpikes = spikeCount; + this->clusters.setSize(2, spikeCount); + + // Copy data to internal structure + clusters.copySubset(data, this->nbSpikes); + + // Determine list of unique cluster + for(int i = 1; i <= this->nbSpikes; i++) { + if(!clusterIds.contains(clusters(1, i))) + clusterIds << clusters(1, i); + } + this->nbClusters = clusterIds.size(); + + //Initialize the variables + this->previousStartTime = 0; + this->previousStartIndex = 1; + this->previousEndIndex = this->nbSpikes; + + this->previousEndTime = static_cast((clusters(2, this->nbSpikes ) ) + 0.5); + + this->fileMaxTime = this->previousEndTime; +} + + +int NWBClustersProvider::loadData() { + // Do nothing, this is all done by fromFile(...) + return OK; +} + +NWBClustersProvider::~NWBClustersProvider() { + +} + +QList NWBClustersProvider::fromFile(const QString& fileUrl, + QStringList channelLabels, + double currentSamplingRate, + dataType fileMaxTime, + int position = 25) { + QList result; + + NWBReader nwbr(fileUrl.toUtf8().constData()); + double samplingRate = nwbr.getSamplingRate(); + QList> NamedSpikes =nwbr.ReadSpikeShank(); + + // Create provider objects + for(int i = 0; i < NamedSpikes.length(); i++) { + int nLen = NamedSpikes[i].length(); + + Array *data = new Array(2, nLen); + for (int idx=0; idx < nLen; ++idx) + { + (*data)(1, idx+1) = i; // spikeData.unit_class; // RHM !!! We may need the proper channel here? + + // The factor of 1000 below converts the HDF5 data from seconds to milliseconds. + // NamedSpikes[] is ZERO based. (*data) is ONE based, hence idx+1. + (*data)(2, idx+1) = static_cast(NamedSpikes[i].arrayData[idx] * 1000); // HDF5 data is in seconds + } + + result.append(new NWBClustersProvider(static_cast(i), + *data, + nLen, + samplingRate, + currentSamplingRate, + fileMaxTime, + position)); + } + return result; +} diff --git a/src/nwbclustersprovider.h b/src/nwbclustersprovider.h new file mode 100644 index 0000000..054d383 --- /dev/null +++ b/src/nwbclustersprovider.h @@ -0,0 +1,43 @@ +#ifndef NWBCLUSTERPROVIDER_H +#define NWBCLUSTERPROVIDER_H + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include +#include "clustersprovider.h" + + +class NWBClustersProvider : public ClustersProvider { + Q_OBJECT + +public: + static QList fromFile(const QString& file, + QStringList channelLabels, + double samplingRate, + dataType fileMaxTime, + int position); + + ~NWBClustersProvider(); + + /**Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + */ + virtual int loadData(); + +private: + NWBClustersProvider(unsigned int channel, + Array& data, + int spikeCount, + double samplingRate, + double currentSamplingRate, + dataType fileMaxTime, + int position); +}; + +#endif // NWBCLUSTERPROVIDER_H diff --git a/src/nwbeventsprovider.cpp b/src/nwbeventsprovider.cpp new file mode 100644 index 0000000..2d9a9fd --- /dev/null +++ b/src/nwbeventsprovider.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * * + * 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 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. * + * * + ***************************************************************************/ +#include +#include "nwbeventsprovider.h" +#include "nwbreader.h" + +NWBEventsProvider::NWBEventsProvider(const QString &fileUrl, int position, int iNWBIndex) + : EventsProvider(".nw"+ QString::number(iNWBIndex) +".evt", 0, position){ + realFileName = fileUrl; + this->iNWBIndex = iNWBIndex; +} + +NWBEventsProvider::~NWBEventsProvider() { + +} + +int NWBEventsProvider::loadData(){ + // Empty previous data + events.setSize(0,0); + timeStamps.setSize(0,0); + + NWBReader nwbr(realFileName.toUtf8().constData()); + NamedArray *nwbEvents = nwbr.ReadEvents(iNWBIndex); + if (!nwbEvents) + return OPEN_ERROR; // INCORRECT_CONTENT; + + this->currentSamplingRate = nwbr.getSamplingRate(); + this->nbEvents = nwbEvents->length(); + + // Extension header information should be extracted here. Right now we do not use the information. + + // Read data packages + + Array tempTimestamps(1, this->nbEvents); + pArray tempDescription; //pArray is missing a constructor + tempDescription.setSize(1, this->nbEvents); + long eventIndex(0L); + long eventsSkipped(0L); + + // Create proper label + EventDescription label = EventDescription(QString::fromUtf8(nwbEvents->strName.c_str())); + for(eventIndex=0; eventIndex < this->nbEvents; ++eventIndex) { + + // Save time and label of event + tempTimestamps[eventIndex] = nwbEvents->arrayData[eventIndex]; + tempDescription[eventIndex] = label; + + // Update event category count + long eventCount = eventDescriptionCounter.contains(label) ? eventDescriptionCounter[label]: 0; + eventDescriptionCounter.insert(label, eventCount + 1); + } + + // Now we know how many events were skipped, we can update the object + this->nbEvents -= eventsSkipped; + + timeStamps.setSize(1, this->nbEvents); + events.setSize(1, this->nbEvents); + + timeStamps.copySubset(tempTimestamps, this->nbEvents); + events.copySubset(tempDescription, this->nbEvents); + + // Update internal struture + this->updateMappingAndDescriptionLength(); + + //for (int jj=1; jj <= this->nbEvents; ++jj) + // qDebug() << "time stamps " << jj << " " << this->timeStamps(1, jj) << " "; + + + //Initialize the variables + previousStartTime = 0; + previousStartIndex = 1; + previousEndIndex = this->nbEvents; + + previousEndTime = static_cast(floor(0.5 + this->timeStamps(1, this->nbEvents))); + fileMaxTime = previousEndTime; + + return OK; +} diff --git a/src/nwbeventsprovider.h b/src/nwbeventsprovider.h new file mode 100644 index 0000000..004e695 --- /dev/null +++ b/src/nwbeventsprovider.h @@ -0,0 +1,32 @@ +#ifndef NWBEVENTSPROVIDER_H +#define NWBEVENTSPROVIDER_H + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include "eventsprovider.h" + +class NWBEventsProvider : public EventsProvider { + Q_OBJECT + +public: + NWBEventsProvider(const QString &fileUrl, int position, int iNWBIndex=0); + ~NWBEventsProvider(); + + /**Loads the event ids and the corresponding spike time. + * @return an loadReturnMessage enum giving the load status + */ + virtual int loadData(); + +private: + int iNWBIndex; + QString realFileName; + +}; + +#endif // NWBEVENTSPROVIDER_H diff --git a/src/nwblocations.cpp b/src/nwblocations.cpp new file mode 100644 index 0000000..191e974 --- /dev/null +++ b/src/nwblocations.cpp @@ -0,0 +1,104 @@ +#include "nwblocations.h" + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +// Here are some example locations that were used during development and testing. +///processing/ecephys/LFP/lfp/data +///processing/ecephys/LFP/lfp/starting_time +///processing/ecephys/LFP/lfp/electrodes +//general/extracellular_ephys/electrodes/shank_electrode_number +//units/spike_times +//units/spike_times_index +//units/electrode_group +///stimulus/presentation/PulseStim_0V_10001ms_LD0/timestamps + +// + +NWBLocations::NWBLocations(std::string hsFileName) +{ + strLoc = getLocationName(hsFileName, "_loc.xml"); +} + + +QString NWBLocations::getLocationName(std::string& s, const std::string& newExt) { + + std::string new_filename = s; + std::string::size_type i = s.rfind('.', s.length()); + + if (i != std::string::npos) + new_filename.replace(i, s.length()-i, newExt); + else + new_filename.append(newExt); + + QString QNewName = QString::fromUtf8(new_filename.c_str()); + return QNewName; +} + +std::string NWBLocations::getVoltageDataSetName() +{ + QString strDSN = "/processing/ecephys/LFP/lfp/data"; + + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + if(reader.parseFile(strLoc,NeuroscopeXmlReader::PARAMETER)){ + strDSN = reader.getNWBDataSetName(); + } + + std::string utf8_text = strDSN.toUtf8().constData(); + return utf8_text; +} + +std::string NWBLocations::getSamplingName() +{ + QString strSN = "/processing/ecephys/LFP/lfp/starting_time"; + + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + if(reader.parseFile(strLoc,NeuroscopeXmlReader::PARAMETER)){ + strSN = reader.getNWBSamplingName(); + } + + std::string utf8_text = strSN.toUtf8().constData(); + return utf8_text; +} + +std::string NWBLocations::getGenericText(std::string strLabel, std::string strDefault) +{ + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + if(reader.parseFile(strLoc,NeuroscopeXmlReader::PARAMETER)){ + QString qsLabel = QString::fromUtf8(strLabel.c_str()); + QString qsDefault = QString::fromUtf8(strDefault.c_str()); + + QString DSN = reader.getGenericText(qsLabel, qsDefault); + + std::string utf8_text = DSN.toUtf8().constData(); + return utf8_text; + } + return ""; +} + +QList NWBLocations::getListGenericTexts(std::string strLabel, std::string strDefault) +{ + NeuroscopeXmlReader reader = NeuroscopeXmlReader(); + if(reader.parseFile(strLoc,NeuroscopeXmlReader::PARAMETER)){ + QString qsLabel = QString::fromUtf8(strLabel.c_str()); + QString qsDefault = QString::fromUtf8(strDefault.c_str()); + + QList DSNs = reader.getListGenericTexts(qsLabel, qsDefault); + + QList lstDSNs = QList(); + for(QString ss : DSNs) + { + std::string utf8_text = ss.toUtf8().constData(); + lstDSNs.append(utf8_text); + } + return lstDSNs; + } + return QList(); +} + diff --git a/src/nwblocations.h b/src/nwblocations.h new file mode 100644 index 0000000..09d6253 --- /dev/null +++ b/src/nwblocations.h @@ -0,0 +1,33 @@ +#ifndef NWBLOCATIONS_H +#define NWBLOCATIONS_H + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "neuroscopexmlreader.h" +#include + +class NWBLocations +{ +public: + NWBLocations(std::string hsFileName); + + std::string getVoltageDataSetName(); + std::string getSamplingName(); + + std::string getGenericText(std::string strLabel, std::string strDefault = ""); + QList getListGenericTexts(std::string strLabel, std::string strDefault); + +private: + NWBLocations(){} // defensive move + QString strLoc; + QString getLocationName(std::string& s, const std::string& newExt); +}; + +#endif // NWBLOCATIONS_H diff --git a/src/nwbreader.cpp b/src/nwbreader.cpp new file mode 100644 index 0000000..fa3d62d --- /dev/null +++ b/src/nwbreader.cpp @@ -0,0 +1,549 @@ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include /* fabs() */ +#include "nwbreader.h" + +#include + +// getGenericText(QString qsTag, QString defaultTextIn) +// /acquisition/test_ephys_data/data +// *** /acquisition/test_ephys_data/timestamps +// /acquisition/test_ephys_data/electrodes + +// /processing/ecephys/LFP/lfp/data +// ***/processing/ecephys/LFP/lfp/starting_time +// /processing/ecephys/LFP/lfp/electrodes + + +VoltageSpecs::VoltageSpecs(int voltageRange, int resolution, int amplification) +{ + this->voltageRange = voltageRange; + this->resolution = resolution; + this->amplification = amplification; +} + + + + +/// +/// \brief NWBReader::bTracesUseTimeStamps checks if timestamps are used. +/// \return true if voltages use time stamps +/// +bool NWBReader::bTracesUseTimeStamps() +{ + std::string strTimeStampLocation = NWB_Locations.getGenericText("nwb_voltage_timestamps", ""); + return (strTimeStampLocation.length() > 0)? true: false; +} + +/// +/// \brief NWBReader::dGetSamplingRateFromTimeStamps estimates sampling rate in Hz based on 1st 2 timestamps +/// \return sampling rate in Hz based on first 2 time stamps +/// +double NWBReader::dGetSamplingRateFromTimeStamps() +{ + double dSamplingRate = 0.0; + + std::string strTimeStampLocation = NWB_Locations.getGenericText("nwb_voltage_timestamps", ""); + if (strTimeStampLocation.length() > 0) + { + double *voltageTimes = nullptr; + long lLengthTS; + HDF5_Utilities.Read1DArray(&voltageTimes, PredType::NATIVE_DOUBLE, lLengthTS, hsFileName, strTimeStampLocation); + if (lLengthTS > 1) + { + double dDiff = voltageTimes[1] - voltageTimes[0]; + if (dDiff > 0.00000001) { + dSamplingRate = 1.0 / dDiff; + } + } + } + + return dSamplingRate; +} + + + + + +NWBReader::NWBReader(std::string hsFileName) + :NWB_Locations(hsFileName) +{ + this->hsFileName = hsFileName; + this->HDF5_Utilities = HDF5Utilities(); +} + +NWBReader::NWBReader() + :NWB_Locations("") +{ +} + +/// +/// \brief NWBReader::ReadNWBAttribs returns some basic information about our trace data +/// \param nbChannels -How many channels of trace data +/// \param resolution -Number of bits of information in the data +/// \param samplingRate -How many samples per second +/// \param offset -Does the trace start at zero +/// \param length -How many points are in the trace +/// +void NWBReader::ReadNWBAttribs(int &nbChannels, int &resolution, double &samplingRate, int &offset, long &length) +{ + try + { + std::cout << "Start ReadNWBAttribs " << std::endl; + + // Turn off the auto-printing when failure occurs so that we can handle the errors appropriately + H5::Exception::dontPrint(); + + H5::H5File* file = new H5::H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + + std::string DSN = NWB_Locations.getVoltageDataSetName(); + DataSet *dataset = new DataSet(file->openDataSet(DSN.c_str())); + + long lDim1 = 0; + H5T_class_t type_class; + HDF5_Utilities.GetDataSetSizes(length, lDim1, resolution, type_class, dataset); + // At this point, length is the number of readings + // !!! We may need to convert this to milliseconds, depending on the sampling rate + nbChannels = static_cast(lDim1); + delete dataset; + + offset = 0; // Did not see this field in NWB, so we placed it here + + // Get the sampling rate from either the time stamps or an attribute + samplingRate = getSamplingRate(); + // !!! Possibly do this in all cases, and not just time stamps + if (bTracesUseTimeStamps()) + { + length = static_cast(length * 1000.0 / samplingRate); // Convert to milliseconds in length, assuming that sampling rate is in Hz + } + + std::cout << "Done ReadNWBAttribs " << std::endl; + + delete file; + } // end of try block + // catch failure caused by the H5File operations + catch (FileIException error) + { + std::cout << "FileIException " << std::endl; + //error.printError(); + return; + } + // catch failure caused by the DataSet operations + catch (DataSetIException error) + { + std::cout << "DataSetIException " << std::endl; + //error.printError(); + return; + } + // catch failure caused by the DataSpace operations + catch (DataSpaceIException error) + { + std::cout << "DataSpaceIException " << std::endl; + //error.printError(); + return; + } + // catch failure caused by the DataSpace operations + catch (DataTypeIException error) + { + std::cout << "DataTypeIException " << std::endl; + //error.printError(); + return; + } + +} + +double NWBReader::getSamplingRate() +{ + double samplingRate = 0.0; + + if (bTracesUseTimeStamps()) + { + samplingRate = dGetSamplingRateFromTimeStamps(); + } + else { + // Get the sampling rate from an attribute + std::string DS = NWB_Locations.getSamplingName(); + H5::H5File* file = new H5::H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + HDF5_Utilities.GetAttributeDouble(samplingRate, file, DS, "rate"); + delete file; + } + + return samplingRate; +} + +long NWBReader::GetNWBLength( ) +{ + //std::cout << "Start GetNWBLength " << std::endl; + int nbChannels = 0; + int resolution = 16; + double samplingRate = 0; + int offset = 0; + long length = 0; + + ReadNWBAttribs(nbChannels, resolution, samplingRate, offset, length); + //std::cout << "Done GetNWBLength " << std::endl; + return length; +} + + + +// Read all channels for a given time range +int NWBReader::iReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS ) +{ + *pVS = nullptr; + + //std::cout << "Start ReadBlockData " << std::endl; + long nbValues = nLength * nChannels; + //std::cout << "nbValues " << nbValues << std::endl; + + std::string DSN = NWB_Locations.getVoltageDataSetName(); + int *data_out = new int[static_cast(nbValues)]; + HDF5_Utilities.Read2DBlockDataNamed(data_out, PredType::NATIVE_INT, iStart, nLength, nChannels, hsFileName, DSN); + + long k = 0; + for (int i = 0; i < nLength; ++i) { + for (int j = 0; j < nChannels; ++j) { + retrieveData[k] = static_cast(data_out[k]); + ++k; + } + } + + delete[] data_out; + + //std::cout << "Done ReadBlockData " << std::endl; + return 0; +} + + +void NWBReader::ReadBlockData2A(Array &retrieveData, int iStart, long nLength, int nChannels, std::string DSN) +{ + //std::cout << "Start ReadBlockData " << std::endl; + long nbValues = nLength * nChannels; + //std::cout << "nbValues " << nbValues << std::endl; + + // Modified by RHM to read Neurodata Without Borders format + int *data_out = new int[static_cast(nbValues)]; + + HDF5_Utilities.Read2DBlockDataNamed(data_out, PredType::NATIVE_INT, iStart, nLength, nChannels, hsFileName, DSN); + + long k = 0; + for (int i = 0; i < nLength; ++i) + for (int j = 0; j < nChannels; ++j) { + //qDebug() << "data_out " << data_out[k] << " " << k << "\n"; + //std::cout << "data_out " << data_out[k] << std::endl; + // RHM thinks that retrieveData is base-1 of 2D and base-0 for 1D + retrieveData[k] = static_cast(data_out[k]); + ++k; + } + delete[] data_out; + + //std::cout << "Done ReadBlockData " << std::endl; +} + + + +int NWBReader::getVoltageGroups(Array& indexData, Array& groupData, int channelNb) +{ + //std::cout << "Start ReadBlockData " << std::endl; + + std::string DSNIndex = NWB_Locations.getGenericText("nwb_voltage_electrodes", "/processing/ecephys/LFP/lfp/electrodes"); + std::string DSNGroup = NWB_Locations.getGenericText("nwb_voltage_electrodes_shanks", "general/extracellular_ephys/electrodes/shank_electrode_number"); + + ReadBlockData2A(indexData, 0, channelNb, 1, DSNIndex); + ReadBlockData2A(groupData, 0, channelNb, 1, DSNGroup); + + // Let's hack a possible bug fix to keep from future array overruns + for (int i=0; i < indexData.nbOfColumns(); ++i) + if (indexData[i] >= channelNb || indexData[i] < -1) + indexData[i] = static_cast(channelNb-1); + + for (int i=0; i < groupData.nbOfColumns(); ++i) + if (groupData[i] >= channelNb || groupData[i] < -1) + groupData[i] = static_cast(channelNb-1); + // End of safety hack + + + return 0; +} + +// An example XML event location is: ///stimulus/presentation/PulseStim_0V_10001ms_LD0/timestamps +NamedArray * NWBReader::ReadEvents(int iPos) +{ + NamedArray *nad = nullptr; + + //std::string DSN = NWB_Locations.getGenericText("nwb_event_times", ""); + //if (DSN.length() < 1) + //{ + // std::cout << "could not find event label in xml file." << std::endl; + // return nad; + //} + + QList lstDSNs = NWB_Locations.getListGenericTexts("nwb_event_times", ""); + if (lstDSNs.count() <= iPos) + { + std::cout << "could not find event label in xml file." << std::endl; + return nad; + } + + try { + H5::H5File* file = new H5::H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + nad = ReadOneEvent(lstDSNs[iPos] /*DSN*/, file); + delete file; + } + // catch failure caused by the H5File operations + catch( FileIException error ) + { + //error.printError(); + return nullptr; + } + // catch failure caused by the DataSet operations + catch( DataSetIException error ) + { + //error.printError(); + return nullptr; + } + // catch failure caused by the DataSpace operations + catch( DataSpaceIException error ) + { + //error.printError(); + return nullptr; + } + // catch failure caused by the DataSpace operations + catch( DataTypeIException error ) + { + //error.printError(); + return nullptr; + } + + return nad; +} + +NamedArray * NWBReader::ReadOneEvent(std::string DSN, H5::H5File* file) +{ + NamedArray *nad = nullptr; + + DataSet *dataset = new DataSet(file->openDataSet(DSN.c_str())); + + // It would be easier to simply strip off the timestamps label and take the name for this event. + // Here is how to get the name from HDF5. + char sz180[180]; + H5Iget_name(dataset->getId(), sz180, 180); + std::cout << "data set name: " << sz180 << std::endl; + // data set name example: /stimulus/presentation/PulseStim_0V_10001ms_LD0/timestamps + + // The event times should be double which is H5T_FLOAT + H5T_class_t type_class = dataset->getTypeClass(); + //std::cout << type_class << std::endl; + + // Confirm we have the correct datatype and read the data. + if (type_class == H5T_FLOAT) + { + // Get the floating point datatype + FloatType floattype = dataset->getFloatType(); + + // Get the dimension size of each dimension in the dataspace + DataSpace dataspace = dataset->getSpace(); + hsize_t dims_out[2]; + int ndims = dataspace.getSimpleExtentDims(dims_out, nullptr); + if (ndims <1) + { + std::cout << "NWBReader::ReadOneEvent() number of dimensions error " << std::endl; + delete dataset; + return nullptr; //nad; + } + + long length = static_cast(dims_out[0]); /*qlonglong*/ + std::cout << " length " << length << std::endl; + + double *data_out = new double[static_cast(length)]; + HDF5_Utilities.Read2DBlockDataNamed(data_out, PredType::NATIVE_DOUBLE, 0, length, 1, hsFileName, DSN); + nad = new NamedArray(); + for (int i=0; iarrayData.append(data_out[i]); + } + nad->strName = sz180; + delete [] data_out; + } + delete dataset; + + return nad; +} + + + + +QList> NWBReader::ReadSpikeShank(std::string nwb_spike_times, std::string nwb_spike_times_index, std::string nwb_units_electrode_group) +{ + // Some example XML locations are: + //nwb_spike_times = "units/spike_times"; + //nwb_spike_times_index = "units/spike_times_index"; + //nwb_units_electrode_group = "units/electrode_group"; + + double *spikeTimes = nullptr; + long lLengthST; + HDF5_Utilities.Read1DArray(&spikeTimes, PredType::NATIVE_DOUBLE, lLengthST, hsFileName, nwb_spike_times); + + int *spikeIndices = nullptr; + long lLengthSI; + HDF5_Utilities.Read1DArray(&spikeIndices, PredType::NATIVE_INT, lLengthSI, hsFileName, nwb_spike_times_index); + + std::string *spikeNames = nullptr; + long lLengthSN; + HDF5_Utilities.Read1DArrayStr(&spikeNames, lLengthSN, hsFileName, nwb_units_electrode_group); + + QList> nad; + for (int idx=0; idx < lLengthSI; ++idx) + { + int ndxLower = (idx > 0) ? spikeIndices[idx-1] : 0; + int ndxUpperP1 = spikeIndices[idx]; + int nLen = ndxUpperP1 - ndxLower; + + NamedArray OneNad; + for (int ii=0; ii < nLen; ++ii) + { + OneNad.arrayData.append(spikeTimes[ndxLower + ii]); + } + OneNad.strName = spikeNames[idx]; + nad.append(OneNad); + + // print debugging information + //std::cout << nad[idx].strName << " "; + //for (int jj=0; jj < 8; ++jj) + // std::cout << nad[idx].arrayData[jj] << " "; + //std::cout << std::endl; + } + + if (spikeTimes) + delete [] spikeTimes; + if (spikeIndices) + delete [] spikeIndices; + if (spikeNames) + delete [] spikeNames; + + return nad; +} + + + +QList> NWBReader::ReadSpikeShank() +{ + // Read the locations from the XML file + // Here some examples: + //units/spike_times + //units/spike_times_index + //units/electrode_group + + std::string DS_STV = NWB_Locations.getGenericText("nwb_spike_times_values", ""); + std::string DS_STI = NWB_Locations.getGenericText("nwb_spike_times_indices", ""); + std::string DS_STS = NWB_Locations.getGenericText("nwb_spike_times_shanks", ""); + + return ReadSpikeShank(DS_STV, DS_STI, DS_STS); +} + + + + + + + +// Read all channels for a given time range +int NWBReader::fReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS) +{ + long nbValues = nLength * nChannels; + + double *data_out = new double[static_cast(nbValues)]; + std::string DSN = NWB_Locations.getVoltageDataSetName(); + HDF5_Utilities.Read2DBlockDataNamed(data_out, PredType::NATIVE_DOUBLE, iStart, nLength, nChannels, hsFileName, DSN); + + + // Find limits of this floating point data + double dMin = data_out[0]; + double dMax = data_out[0]; + for (long ll = 1; ll < nbValues; ++ll) + { + if (data_out[ll] > dMax) + dMax = data_out[ll]; + if (data_out[ll] < dMin) + dMin = data_out[ll]; + } + double dRange = dMax - dMin; + //double dFact = (fabs(dMax) > fabs(dMin))? fabs(dMax) : fabs(dMin); + //dFact *= 0.032; + //if (dFact < 1) + // dFact = 1.0; + + *pVS = new VoltageSpecs(10, 16, 1000); + //VoltageSpecs(int voltageRange, int resolution, int amplification); + + + // Convert the floating point data to short integers to make it compatible with Neuroscope + // !!! See what neuroscope attributes we must update to make clear that we converted. say scale. + // short integers range from -32,768 to 32,767, so their range is 65535. + double dFact = (fabs(dRange)< 0.00001) ? 1.0 : 65535.0/dRange; + double dTemp= 0.0; + long k = 0; + for (int i = 0; i < nLength; ++i) { + for (int j = 0; j < nChannels; ++j) { + dTemp = -32768.0 + dFact * (data_out[k]-dMin); + //dTemp = 0.0 + dFact * (data_out[k]-dMin)/2000.0; + retrieveData[k] = static_cast(dTemp); + ++k; + } + } + + delete[] data_out; + + return 0; +} + + + + + + +int NWBReader::getDataSetDataTypeFromLabel(H5T_class_t &type_class, std::string DSN) +{ + try { + H5::H5File* file = new H5::H5File(hsFileName.c_str(), H5F_ACC_RDONLY); + + DataSet *dataset = new DataSet(file->openDataSet(DSN.c_str())); + type_class = dataset->getTypeClass(); + delete dataset; + + delete file; + return 0; + } + catch(...) { + type_class = H5T_class_t::H5T_NO_CLASS; + std::cout << " Error determining data set type " << std::endl; + return 1; + } +} + +int NWBReader::ReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS) +{ + H5T_class_t type_class; + std::string DSN = NWB_Locations.getVoltageDataSetName(); + int iRet = getDataSetDataTypeFromLabel(type_class, DSN); + if (iRet == 0) + { + if (type_class == H5T_INTEGER) + return iReadVoltageTraces(retrieveData, iStart, nLength, nChannels, pVS); + else if (type_class == H5T_FLOAT) + return fReadVoltageTraces(retrieveData, iStart, nLength, nChannels, pVS); + } + std::cout << " Error determining data set type for reading voltage traces." << std::endl; + return 1; +} + + + diff --git a/src/nwbreader.h b/src/nwbreader.h new file mode 100644 index 0000000..68a9e7b --- /dev/null +++ b/src/nwbreader.h @@ -0,0 +1,77 @@ +#ifndef NWBREADER_H +#define NWBREADER_H + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ +#include "nwblocations.h" +#include "hdf5utilities.h" + +// Helper class to attach a label to a list/array of information. +template +class NamedArray { +public: + NamedArray(){} + std::string strName; + QList arrayData; + int length() {return arrayData.length();} +}; + +class VoltageSpecs { +public: + int voltageRange; + int resolution; + int amplification; + VoltageSpecs(int voltageRange, int resolution, int amplification); +}; + +/// +/// \brief The NWBReader class ties together other classes so that NWB data can be sent to Neuroscope. +/// The HDF5 file details are handled using HDF5Utilities that knows little or nothing about Neuroscope. +/// The data locations within HDF5 files are handled using class: NWBLocations. +/// Thus, NWBReader is the glue between Neuroscope and HDF5. +/// +class NWBReader +{ +public: + NWBReader(std::string hsFileName); + + void ReadNWBAttribs(int &nbChannels, int &resolution, double &samplingRate, int &offset, long &length); + long GetNWBLength(); + + int ReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS); + + int getVoltageGroups(Array& indexData, Array& groupData, int channelNb); + + QList> ReadSpikeShank(std::string nwb_spike_times, std::string nwb_spike_times_index, std::string nwb_units_electrode_group); + QList> ReadSpikeShank(); + + NamedArray * ReadEvents(int iPos); + + void ReadBlockData2A(Array &retrieveData, int iStart, long nLength, int nChannels, std::string DSN); + + double getSamplingRate(); + bool bTracesUseTimeStamps(); + double dGetSamplingRateFromTimeStamps(); + +private: + NWBReader(); // defensive move + std::string hsFileName; + NWBLocations NWB_Locations; + HDF5Utilities HDF5_Utilities; + + //int ReadVoltageTraces(int *data_out, int iStart, long nLength, int nChannels); + NamedArray * ReadOneEvent(std::string DSN, H5::H5File* file); + + int getDataSetDataTypeFromLabel(H5T_class_t &type_class, std::string DSN); + int iReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS); + int fReadVoltageTraces(Array &retrieveData, int iStart, long nLength, int nChannels, VoltageSpecs **pVS); +}; + + +#endif // NWBREADER_H diff --git a/src/nwbtracesprovider.cpp b/src/nwbtracesprovider.cpp new file mode 100644 index 0000000..e20621b --- /dev/null +++ b/src/nwbtracesprovider.cpp @@ -0,0 +1,188 @@ +/*************************************************************************** + nwbtracesprovider.cpp - description + + Processes Neurodata Without Borders data .nwb + Adapted by Robert H. Moore (RHM) for Dr. Ben Dichter. + + Adapted from nsxtracesprovider.h: + ------------------- + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "nwbtracesprovider.h" + +#include +#include +#include "nwbreader.h" + + +NWBTracesProvider::NWBTracesProvider(const QString &fileName) + : AltTracesProvider(fileName, -1, 16, 20, 1000, 0, 0) +{ + mInitialized = false; +} + +NWBTracesProvider::~NWBTracesProvider() { + //if(mInitialized) + // delete[] mExtensionHeaders; +} + +bool NWBTracesProvider::init() { + // Do not initialize twice + if(mInitialized) + return true; + + int nbChannels; int resolution; double samplingRate; int offset; long length; + NWBReader *nwbFile = new NWBReader(this->fileName.toUtf8().constData()); + nwbFile->ReadNWBAttribs(nbChannels, resolution, samplingRate, offset, length); + delete nwbFile; + this->nbChannels = nbChannels; + this->resolution = resolution; + this->samplingRate = samplingRate; + this->offset = 0; + this->length = length; + + mInitialized = true; + return true; +} + +long NWBTracesProvider::getNbSamples(long start, long end, long startInRecordingUnits) { + // Check if startInRecordingUnits was supplied, else compute it. + if(startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + + // The caller should have checked that we do not go over the end of the file. + // The recording starts at time equals 0 and ends at length of the file minus one. + // Therefore the sample at endInRecordingUnits is never returned. + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + + return endInRecordingUnits - startInRecordingUnits; +} + +void NWBTracesProvider::retrieveData(long start, long end, QObject* initiator, long startInRecordingUnits) { + Array data; + + if(!mInitialized) { + qDebug() << "no init!"; + emit dataReady(data,initiator); + return; + } + + // Check if startInRecordingUnits was supplied, else compute it. + if(startInRecordingUnits == 0) + startInRecordingUnits = this->samplingRate * start / 1000.0; + // The caller should have checked that we do not go over the end of the file. + // RHM believes that the caller may not have checked, so some zero padding is added when necessary. + + long endInRecordingUnits = (this->samplingRate * end / 1000.0); + dataType nbSamples = static_cast(endInRecordingUnits - startInRecordingUnits) + 1; + + qint64 nbValues = 0;// nbSamples * this->nbChannels; + //data.setSize(nbValues, this->nbChannels); + + if (startInRecordingUnits+nbSamples >= this->length) + { + // make the array very large anyway, even if we are past the end + nbValues = nbSamples * this->nbChannels; + data.setSize(nbValues, this->nbChannels); + // Now zero pad the array + for(qint64 i = 0; i < nbValues; ++i) + data[i] = 0; + if (startInRecordingUnits >= this->length-1) + { + emit dataReady(data,initiator); + return; + } + + // Now shorten the sized for the HDF5 call + nbSamples = this->length - startInRecordingUnits; + if (nbSamples <=0) + nbSamples=1; + nbValues = nbSamples * this->nbChannels; + } + else + { + // We are not past the end of the file + nbValues = nbSamples * this->nbChannels; + data.setSize(nbValues, this->nbChannels); + } + + + qDebug() << "NWS"; + // Modified by RHM to read Neurodata Without Borders format + NWBReader nwbr(this->fileName.toUtf8().constData()); + + + VoltageSpecs *pVS; + Array traceData(nbSamples,nbChannels); + nwbr.ReadVoltageTraces(traceData, startInRecordingUnits, nbSamples, this->nbChannels, &pVS); + qDebug() << "nbSamples " << nbSamples; + if (pVS) + { + this->voltageRange = pVS->voltageRange; + this->resolution = pVS->resolution; + this->amplification = pVS->amplification; + } + + // Compute acquisition gain + double acquisitionGain = (voltageRange * 1000000) / (pow(2.0, resolution) * amplification); + + //Apply the offset if need it and store the values in data. + if(offset != 0){ + for(qint64 i = 0; i < nbValues; ++i) + data[i] = round(traceData[i] - offset * acquisitionGain); + } + else{ + for(qint64 i = 0; i < nbValues; ++i){ + data[i] = round(traceData[i] * acquisitionGain); + //if (i < 20) + // qDebug() << "data A2D " << traceData[i] << " " << data[i]; + } + } + + //qDebug() << "About to emit"; + //Send the information to the receiver. + emit dataReady(data,initiator); + +} + +QStringList NWBTracesProvider::getLabels() { + QStringList labels; + + // Initialize to group "0" at first, in case the groups are not listed + for(int i = 0; i < this->nbChannels; i++) { + labels << QString("0"); + } + + long nbSamples = 1; + Array indexData(nbSamples,this->nbChannels); + Array groupData(nbSamples,this->nbChannels); + NWBReader nwbr(this->fileName.toUtf8().constData()); + nwbr.getVoltageGroups(indexData, groupData, this->nbChannels); + + int nLabels = labels.length(); + for (int i=0; inbChannels; ++i) + { + //qDebug() << "index data " << i << " " << channelNb << indexData[i+1] << "\n"; + int iIndex = indexData[i]; + //int iGroup = groupData[iIndex]+1; + + //labels[iIndex] = QString::number(iGroup); + + if (iIndex >= nLabels) { + iIndex = indexData[i] = static_cast(nLabels - 1); + } + labels[iIndex] = QString::number(i); + } + + return labels; +} diff --git a/src/nwbtracesprovider.h b/src/nwbtracesprovider.h new file mode 100644 index 0000000..875143b --- /dev/null +++ b/src/nwbtracesprovider.h @@ -0,0 +1,89 @@ + +/*************************************************************************** + nwbtracesprovider.h - description + ------------------- + + Processes Neurodata Without Borders data .nwb + Adapted by Robert H. Moore (RHM) for Dr. Ben Dichter. + + Adapted from nsxtracesprovider.h: + copyright : (C) 2015 by Florian Franzen + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef NWBTRACESPROVIDER_H +#define NWBTRACESPROVIDER_H + + +//include files for the application +#include "alttracesprovider.h" +#include "array.h" +#include "types.h" + + +// include files for QT +#include +#include + +/** Class providing the row recorded data (contained in a .nwb file). + * @author Florian Franzen - adapted by Robert H. Moore + */ + +class NWBTracesProvider : public AltTracesProvider { + Q_OBJECT +public: + + /**Constructor. + * @param fileUrl the url of the file containing the data provided by this class. + */ + NWBTracesProvider(const QString &fileUrl); + virtual ~NWBTracesProvider(); + + /** Initializes the object by reading the nwb files header. + */ + virtual bool init(); + + /**Computes the number of samples between @p start and @p end. + * @param start begining of the time frame from which the data have been retrieved, given in milisecond. + * @param end end of the time frame from which to retrieve the data, given in milisecond. + * @return number of samples in the given time frame. + * @param startTimeInRecordingUnits begining of the time frame from which the data have been retrieved, given in recording units. + */ + virtual long getNbSamples(long start, long end, long startInRecordingUnits); + + /** Return the labels of each channel as read from nwb file. */ + virtual QStringList getLabels(); + +Q_SIGNALS: + /**Signals that the data have been retrieved. + * @param data array of data in uV (number of channels X number of samples). + * @param initiator instance requesting the data. + */ + void dataReady(Array& data,QObject* initiator); + +protected: + virtual QString qsShortName() {return "NWB";} + +private: + + /**Retrieves the traces included in the time frame given by @p startTime and @p endTime. + * @param startTime begining of the time frame from which to retrieve the data, given in milisecond. + * @param endTime end of the time frame from which to retrieve the data, given in milisecond. + * @param initiator instance requesting the data. + * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. + */ + virtual void retrieveData(long startTime, long endTime, QObject* initiator, long startTimeInRecordingUnits); + +}; + + + +#endif // NWBTRACESPROVIDER_H diff --git a/src/parameterxmlmodifier.cpp b/src/parameterxmlmodifier.cpp index 2c24b1b..1dcf38a 100644 --- a/src/parameterxmlmodifier.cpp +++ b/src/parameterxmlmodifier.cpp @@ -156,7 +156,7 @@ QDomNode ParameterXmlModifier::findDirectChild(const QString& childName,const QS // the node really is an element and has the right tag. if(child.isElement() && child.nodeName() == childName){ QDomNodeList list = child.childNodes(); - for(uint i=0;i +#include "positionpropertieslayout.h" /**Class representing the NeuroScope position preferences tab contained in the preferences or properties dialogs. *@author Lynn Hazan diff --git a/src/positionsprovider.h b/src/positionsprovider.h index 283fc8f..4e0105b 100644 --- a/src/positionsprovider.h +++ b/src/positionsprovider.h @@ -19,7 +19,7 @@ #define POSITIONSPROVIDER_H //include files for the application -#include +#include "dataprovider.h" #include #include diff --git a/src/positionview.h b/src/positionview.h index 767a643..e151822 100644 --- a/src/positionview.h +++ b/src/positionview.h @@ -31,7 +31,7 @@ // application specific includes -#include +#include "baseframe.h" #include "positionsprovider.h" #include #include "eventdata.h" diff --git a/src/prefdefaults.h b/src/prefdefaults.h index 0fbdcc1..69d2a0b 100644 --- a/src/prefdefaults.h +++ b/src/prefdefaults.h @@ -30,7 +30,7 @@ //include files for the application -#include +#include "prefdefaultslayout.h" /** * Class representing the NeuroScope Defaults Configuration page of the Neuroscope preferences dialog. diff --git a/src/prefgeneral.h b/src/prefgeneral.h index 904fec4..3e56274 100644 --- a/src/prefgeneral.h +++ b/src/prefgeneral.h @@ -33,7 +33,7 @@ //include files for the application -#include +#include "prefgenerallayout.h" /** * Class representing the Neuroscope Configuration page of the Neuroscope preferences dialog. diff --git a/src/prefgenerallayout.ui b/src/prefgenerallayout.ui index aa56eca..9ec28d9 100644 --- a/src/prefgenerallayout.ui +++ b/src/prefgenerallayout.ui @@ -228,7 +228,7 @@ QGroupBox QGroupBox -
Qt3Support/QGroupBox
+
QGroupBox
1
diff --git a/src/properties.h b/src/properties.h index dc846e8..ac26128 100644 --- a/src/properties.h +++ b/src/properties.h @@ -32,7 +32,7 @@ //include files for the application -#include +#include "propertieslayout.h" diff --git a/src/propertieslayout.ui b/src/propertieslayout.ui index 13f4a48..54081b8 100644 --- a/src/propertieslayout.ui +++ b/src/propertieslayout.ui @@ -619,7 +619,7 @@ QGroupBox QGroupBox -
Qt3Support/QGroupBox
+
QGroupBox
1
diff --git a/src/tags.cpp b/src/tags.cpp index a43d0e2..e755b59 100644 --- a/src/tags.cpp +++ b/src/tags.cpp @@ -143,5 +143,11 @@ extern const QString CHANNEL_POSITION = "channelPosition"; //Tag included in CHANNEL_POSITION extern const QString GAIN = "gain"; +//Tag included in NWB_DATASET_NAME - RHM +extern const QString NWB_DATASET_NAME = "nwb_dataset_name"; + +//Tag included in NWB_SAMPLING_NAME - RHM +extern const QString NWB_SAMPLING_NAME = "nwb_sampling_name"; + } diff --git a/src/tags.h b/src/tags.h index 9b187e1..975cd5e 100644 --- a/src/tags.h +++ b/src/tags.h @@ -224,6 +224,13 @@ extern const QString CHANNEL_POSITION; /**Tag for the gain element included in the channelPosition element.*/ extern const QString GAIN; + +/**Tag for Neurodata Without Borders files that point to the data. Added by Robert H. Moore (RHM) */ +extern const QString NWB_DATASET_NAME; + +/**Tag for Neurodata Without Borders files that point to the data. Added by Robert H. Moore (RHM) */ +extern const QString NWB_SAMPLING_NAME; + } #endif diff --git a/src/tracesprovider.cpp b/src/tracesprovider.cpp index 9ed543f..aae997a 100644 --- a/src/tracesprovider.cpp +++ b/src/tracesprovider.cpp @@ -27,14 +27,33 @@ #include #include #include - + + // include c/c++ headers #include -TracesProvider::TracesProvider(const QString& fileUrl,int nbChannels,int resolution,double samplingRate,int offset) +//include files for c/c++ libraries +#include + + +#if defined(_MSC_VER) || defined(__MINGW64_VERSION_MAJOR) + #define fseeko64(stream, offset, whence) _fseeki64(stream, offset, whence) + #define ftello64(stream) _ftelli64(stream) +#elif defined(__APPLE__) + #define fseeko64(stream, offset, whence) fseeko(stream, offset, whence) +#else + #define fseeko(stream, offset, whence) fseeko64(stream, offset, whence) + #define ftello(stream) ftello64(stream) +#endif + + + +TracesProvider::TracesProvider(const QString &fileUrl, int nbChannels, int resolution, int voltageRange, int amplification, double samplingRate, int offset) : DataProvider(fileUrl), nbChannels(nbChannels), resolution(resolution), + voltageRange(voltageRange), + amplification(amplification), samplingRate(samplingRate), offset(offset) { @@ -123,13 +142,17 @@ void TracesProvider::retrieveData(long startTime,long endTime,QObject* initiator //data will contain the final values. data.setSize(nbSamples,nbChannels); + // Compute acquisition gain + double acquisitionGain = (voltageRange * 1000000) / (pow(2.0, resolution) * amplification); + //Depending on the acquisition system resolution, the data are store as short or long if((resolution == 12) | (resolution == 14) | (resolution == 16)){ - Array retrieveData(nbSamples,nbChannels); + Array retrieveData(nbSamples,nbChannels); qint64 nbValues = nbSamples * nbChannels; + // Is this a Neuralynx file? - int p = fileName.lastIndexOf(".ncs"); - if ( p != -1 ) + int pNCS = fileName.lastIndexOf(".ncs"); + if ( pNCS != -1 ) { qDebug()<<"NCS"; /// Modified by M.Zugaro to read Neuralynx ncs format @@ -254,11 +277,11 @@ void TracesProvider::retrieveData(long startTime,long endTime,QObject* initiator qint64 position = static_cast(static_cast(startInRecordingUnits)* static_cast(nbChannels)); - dataFile.seek(position * sizeof(short)); - qint64 nbRead = dataFile.read(reinterpret_cast(&retrieveData[0]), sizeof(short) * nbValues); + dataFile.seek(position * sizeof(int16_t)); + qint64 nbRead = dataFile.read(reinterpret_cast(&retrieveData[0]), sizeof(int16_t) * nbValues); // copy the data into retrieveData. - if(nbRead != qint64(nbValues*sizeof(short))){ + if(nbRead != qint64(nbValues*sizeof(int16_t))){ //emit the signal with an empty array, the reciever will take care of it, given a message to the user. data.setSize(0,0); dataFile.close(); @@ -267,17 +290,19 @@ void TracesProvider::retrieveData(long startTime,long endTime,QObject* initiator } dataFile.close(); } + qDebug() << "Transfer to data " << nbValues << " " << offset; //Apply the offset if need it,convert to dataType and store the values in data. if(offset != 0){ for(qint64 i = 0; i < nbValues; ++i){ - data[i] = static_cast(retrieveData[i]) - static_cast(offset); + data[i] = round(static_cast(retrieveData[i]) - offset * acquisitionGain); } } else { - for(qint64 i = 0; i < nbValues; ++i){ - data[i] = static_cast(retrieveData[i]); + data[i] = round(static_cast(retrieveData[i]) * acquisitionGain); } } + qDebug() << "End Transfer to data"; + } else if(resolution == 32) { QFile dataFile(fileName); @@ -290,11 +315,11 @@ void TracesProvider::retrieveData(long startTime,long endTime,QObject* initiator qint64 nbValues = nbSamples * nbChannels; qint64 position = static_cast(static_cast(startInRecordingUnits)* static_cast(nbChannels)); - dataFile.seek(position * sizeof(short)); - qint64 nbRead = dataFile.read(reinterpret_cast(&retrieveData[0]), sizeof(short) * nbValues); + dataFile.seek(position * sizeof(int32_t)); + qint64 nbRead = dataFile.read(reinterpret_cast(&retrieveData[0]), sizeof(int32_t) * nbValues); // copy the data into retrieveData. - if(nbRead != qint64(nbValues*sizeof(short))){ + if(nbRead != qint64(nbValues*sizeof(int32_t))){ //emit the signal with an empty array, the reciever will take care of it, given a message to the user. data.setSize(0,0); dataFile.close(); @@ -304,23 +329,24 @@ void TracesProvider::retrieveData(long startTime,long endTime,QObject* initiator //Apply the offset if need it and store the values in data. if(offset != 0){ for(qint64 i = 0; i < nbValues; ++i) - data[i] = retrieveData[i] - static_cast(offset); + data[i] = round(retrieveData[i] - offset * acquisitionGain); } else{ for(qint64 i = 0; i < nbValues; ++i){ - data[i] = static_cast(retrieveData[i]); + data[i] = round(retrieveData[i] * acquisitionGain); } } //The data have been retrieve, close the file. dataFile.close(); } - + //qDebug() << "About to emit"; //Send the information to the receiver. emit dataReady(data,initiator); } void TracesProvider::computeRecordingLength(){ + qDebug() << "Inside computeRecordingLength"; //When the bug in gcc will be corrected for the 64 bits the c++ code will be use //[alex@slut]/home/alex/src/sizetest > ./sizetest-2.95.3 // sizeof(std::streamoff) = 8 bytes (64 bits) @@ -355,8 +381,8 @@ void TracesProvider::computeRecordingLength(){ else if(resolution == 32) dataSize = 4; // Is this a Neuralynx file? - int p = fileName.lastIndexOf(".ncs"); - if ( p != -1 ) + int pNCS = fileName.lastIndexOf(".ncs"); + if ( pNCS != -1 ) { /// Modified by M.Zugaro to read Neuralynx ncs format @@ -387,8 +413,19 @@ void TracesProvider::computeRecordingLength(){ ) * 1000 ); } + qDebug() << "Exiting computeRecordingLength"; } long TracesProvider::getTotalNbSamples(){ return static_cast((length * samplingRate) / 1000); } + +QStringList TracesProvider::getLabels() { + QStringList labels; + + for(int i = 0; i < this->nbChannels; i++) { + labels << QString::number(i); + } + + return labels; +} diff --git a/src/tracesprovider.h b/src/tracesprovider.h index 216ffa6..a5a3121 100644 --- a/src/tracesprovider.h +++ b/src/tracesprovider.h @@ -19,12 +19,13 @@ #define TRACESPROVIDER_H //include files for the application -#include +#include "dataprovider.h" #include #include // include files for QT #include +#include /**Class providing the row recorded data (contained in a .dat or .eeg file). *@author Lynn Hazan @@ -42,8 +43,8 @@ class TracesProvider : public DataProvider { * @param samplingRate sampling rate used to record the data contained in the file identified by fileUrl. * @param offset offset to apply to the data contained in the file identified by fileUrl. */ - TracesProvider(const QString &fileUrl, int nbChannels, int resolution, double samplingRate, int offset); - ~TracesProvider(); + TracesProvider(const QString &fileUrl, int nbChannels, int resolution, int voltageRange, int amplification, double samplingRate, int offset); + virtual ~TracesProvider(); /// Added by M.Zugaro to enable automatic forward paging void updateRecordingLength() { computeRecordingLength(); } @@ -59,7 +60,7 @@ class TracesProvider : public DataProvider { /**Sets the number of channels corresponding to the file identified by fileUrl. * @param nb the number of channels. */ - void setNbChannels(int nb){ + virtual void setNbChannels(int nb){ nbChannels = nb; computeRecordingLength(); } @@ -67,7 +68,7 @@ class TracesProvider : public DataProvider { /**Sets the resolution used to record the data contained in the file identified by fileUrl. * @param res resolution. */ - void setResolution(int res){ + virtual void setResolution(int res){ resolution = res; computeRecordingLength(); } @@ -75,11 +76,25 @@ class TracesProvider : public DataProvider { /**Sets the sampling rate used to record the data contained in the file identified by fileUrl. * @param rate the sampling rate. */ - void setSamplingRate(double rate){ + virtual void setSamplingRate(double rate){ samplingRate = rate; computeRecordingLength(); } + /**Sets the voltage range used to record the data contained in the file identified by fileUrl. + * @param range the voltage range. + */ + virtual void setVoltageRange(int range){ + voltageRange = range; + } + + /**Sets the amplification used to record the data contained in the file identified by fileUrl. + * @param value the amplification. + */ + virtual void setAmplification(int value){ + amplification = value; + } + /**Sets the offset to apply to the data contained in the file identified by fileUrl. * @param newOffset offset. */ @@ -97,6 +112,13 @@ class TracesProvider : public DataProvider { */ double getSamplingRate() const {return samplingRate;} + /**Returns the voltage range used to record the data contained in the file identified by fileUrl. + */ + double getVoltageRange() const {return voltageRange;} + + /**Returns the amplification used to record the data contained in the file identified by fileUrl. */ + double getAmplification() const {return amplification;} + /**Returns the offset to apply to the data contained in the file identified by fileUrl. */ int getOffset() const {return offset;} @@ -110,25 +132,45 @@ class TracesProvider : public DataProvider { * @return number of samples in the given time frame. * @param startTimeInRecordingUnits begining of the time frame from which the data have been retrieved, given in recording units. */ - dataType getNbSamples(long startTime,long endTime,long startTimeInRecordingUnits); + virtual dataType getNbSamples(long startTime,long endTime,long startTimeInRecordingUnits); /**Returns the total number of samples in recorded contained in the file identified by fileUrl.*/ long getTotalNbSamples(); + /** Return the label for each channel, by default just the ID of the channel. */ + virtual QStringList getLabels(); + +public Q_SLOTS: + /** Called when paging is started. + * Usefull for trace providers that have live data sources. + */ + virtual void slotPagingStarted() {} + + /** Called when paging is stopped. + * Usefull for trace providers that have live data sources. + */ + virtual void slotPagingStopped() {} + Q_SIGNALS: /**Signals that the data have been retrieved. - * @param data array of data (number of channels X number of samples). + * @param data array of data in uV (number of channels X number of samples). * @param initiator instance requesting the data. */ - void dataReady(Array& data,QObject* initiator); + virtual void dataReady(Array& data, QObject* initiator); -private: +protected: /**Number of channels used to record the data.*/ int nbChannels; /**Resolution of the acquisition system used to record the data.*/ int resolution; + /**Voltage range of acquisition system used to record the data*/ + int voltageRange; + + /**Amplification of acquisition system used to record the data*/ + int amplification; + /**Sampling rate used to record the data.*/ double samplingRate; @@ -146,11 +188,14 @@ class TracesProvider : public DataProvider { * @param initiator instance requesting the data. * @param startTimeInRecordingUnits begining of the time interval from which to retrieve the data in recording units. */ - void retrieveData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); + virtual void retrieveData(long startTime,long endTime,QObject* initiator,long startTimeInRecordingUnits); /**Computes the total length of the document in miliseconds.*/ - void computeRecordingLength(); + virtual void computeRecordingLength(); + static inline dataType round(double d) { + return static_cast( (d > 0.0) ? d + 0.5 : d - 0.5); + } }; #endif diff --git a/src/traceview.cpp b/src/traceview.cpp index a22802a..2106eac 100644 --- a/src/traceview.cpp +++ b/src/traceview.cpp @@ -45,9 +45,10 @@ const int TraceView::XMARGIN = 50; const int TraceView::YMARGIN = 0; +const float TraceView::U_THETA = 400.0f; TraceView::TraceView(TracesProvider& tracesProvider,bool greyScale,bool multiColumns,bool verticalLines, - bool raster,bool waveforms,bool labelsDisplay,QList& channelsToDisplay,int unitGain,int acquisitionGain,long start,long timeFrameWidth, + bool raster,bool waveforms,bool labelsDisplay,QList& channelsToDisplay, float screenGain,long start,long timeFrameWidth, ChannelColors* channelColors,QMap >* groupsChannels,QMap* channelsGroups, bool autocenterChannels,QList& channelOffsets,QList& gains,const QList& skippedChannels,int rasterHeight,const QImage& backgroundImage,QWidget* parent, const char* name,const QColor& backgroundColor,QStatusBar* statusBar, @@ -69,8 +70,7 @@ TraceView::TraceView(TracesProvider& tracesProvider,bool greyScale,bool multiCol channelsGroups(channelsGroups), doublebuffer(), background(backgroundImage), - acquisitionGain(acquisitionGain), - unitGain(unitGain), + screenGain(screenGain), xMargin(10), yMargin(0), columnDisplayChanged(false), @@ -130,7 +130,7 @@ TraceView::TraceView(TracesProvider& tracesProvider,bool greyScale,bool multiCol //Set the drawing variables nbChannels = tracesProvider.getNbChannels(); - int samplingRate = tracesProvider.getSamplingRate(); + int samplingRate = static_cast(tracesProvider.getSamplingRate()); updateShownGroupsChannels(shownChannels); @@ -165,13 +165,11 @@ TraceView::TraceView(TracesProvider& tracesProvider,bool greyScale,bool multiCol //The initial amplitude and factor for each channel. if (gains.isEmpty()) { - setGains(unitGain,acquisitionGain); + setGains(screenGain); } else { - //Compute alpha: (3.traceVspace) / (Utheta . acquisitionGain) - //Utheta: amplitude maximal of theta in milivolts, 0.4 mv - alpha = static_cast(static_cast(3 * traceVspace) / - static_cast(0.4 * static_cast(acquisitionGain))); - //factor = alpha * (4/3)^gain + //Compute alpha: (3 . traceVspace) / U_theta + alpha = (3.0f * traceVspace) / U_THETA; + //factor = alpha * (3/4)^gain for(int i = 0; i < nbChannels; ++i){ if (i(alpha * pow(0.75,gains[i])); @@ -254,6 +252,7 @@ void TraceView::dataAvailable(Array& data,QObject* initiator) QMessageBox::critical(this, tr("IO Error"),tr("An error has occured, the data file could not be opened or the file size is incorrect.")); changeCursor(); + emit dataError(); return; } @@ -500,11 +499,12 @@ void TraceView::displayTimeFrame(long start,long timeFrameWidth){ QHashIterator iterator2(eventsData); while (iterator2.hasNext()) { iterator2.next(); - if (iterator2.key() != eventProviderToSkip){ + if (iterator2.key() != eventProviderToSkip){ static_cast(eventProviders[iterator2.key()])->requestData(startTime,endTime,this); } else eventProviderToSkip.clear(); } + } tracesProvider.requestData(startTime,endTime,this,startTimeInRecordingUnits); @@ -664,6 +664,7 @@ void TraceView::paintEvent ( QPaintEvent*){ painter.setClipRect(painter.window()); painter.setClipping(true); + //Paint all the traces in the shownChannels list (in the double buffer,on top of the background image or the background color ) drawTraces(painter); @@ -829,7 +830,7 @@ void TraceView::resetOffsets(const QMap& selectedChannelDefaultOffsets) void TraceView::resetGains(const QList& selectedChannels){ - //factor = alpha * (4/3)^gain, gain equals 0 at the begining + //factor = alpha * (3/4)^gain, gain equals 0 at the begining QList::const_iterator iterator; for(iterator = selectedChannels.begin(); iterator != selectedChannels.end(); ++iterator){ @@ -914,16 +915,13 @@ void TraceView::groupsModified(bool active){ update(); } -void TraceView::setGains(int gain,int acquisitionGain){ - unitGain = gain; - this->acquisitionGain = acquisitionGain; +void TraceView::setGains(float gain){ + screenGain = gain; - //Compute alpha: (3.traceVspace) / (Utheta . acquisitionGain) - //Utheta: amplitude maximal of theta in milivolts, 0.4 mv - alpha = static_cast(static_cast(3 * traceVspace) / - static_cast(0.4 * static_cast(acquisitionGain))); + //Compute alpha: (3.traceVspace) / (Utheta) + alpha = (3.0f * traceVspace) / U_THETA; - //factor = alpha * (4/3)^gain, gain equals 0 at the begining + //factor = alpha * (3/4)^gain, gain equals 0 at the begining gains.clear(); channelFactors.clear(); @@ -941,11 +939,11 @@ void TraceView::setGains(int gain,int acquisitionGain){ void TraceView::computeChannelDisplayGain(){ - // Those gains are computed as (unitGain.alpha / screenResolution) .(world-viewport height ratio) .channelFactor). + // Those gains are computed as (screenGain.alpha / screenResolution) .(world-viewport height ratio) .channelFactor). QRectF r((QRectF)window); qreal heightRatio = viewport.height() / r.height(); - qreal beta = static_cast((static_cast(unitGain) * alpha)/ static_cast(screenResolution)) * heightRatio; + qreal beta = static_cast((screenGain * 1000 * alpha) / static_cast(screenResolution)) * heightRatio; channelDisplayGains.clear(); @@ -956,10 +954,10 @@ void TraceView::computeChannelDisplayGain(){ } void TraceView::computeChannelDisplayGain(const QList& channelIds){ - // Those gains are computed as (unitGain.alpha / screenResolution) .(world-viewport height ratio) .0.75^gain[i]). + // Those gains are computed as (screenGain.alpha / screenResolution) .(world-viewport height ratio) .0.75^gain[i]). QRect r((QRect)window); float heightRatio = static_cast(static_cast(viewport.height()) / static_cast(r.height())); - float beta = static_cast((static_cast(unitGain) * alpha)/ static_cast(screenResolution)) * heightRatio; + float beta = static_cast((screenGain * 1000 * alpha)/ static_cast(screenResolution)) * heightRatio; for(int i = 0; i < channelIds.size(); ++i){ int channelId = channelIds.at(i); @@ -970,7 +968,7 @@ void TraceView::computeChannelDisplayGain(const QList& channelIds){ void TraceView::increaseAllAmplitude(){ //Increases the ordinate scale resulting in //an reduction of the traces in the ordinate direction. - //factor = traceVspace / ((4/3)^gain * unitGain) + //factor = traceVspace / ((3/4)^gain * screenGain) for(int i = 0; i < nbChannels; ++i){ gains[i]--; channelFactors[i] = static_cast(alpha * pow(0.75,gains.at(i))); @@ -987,7 +985,7 @@ void TraceView::increaseAllAmplitude(){ void TraceView::decreaseAllAmplitude(){ //Decreases the ordinate scale resulting in //an enlargement of the traces in the ordinate direction. - //factor = traceVspace / ((4/3)^gain * unitGain) + //factor = traceVspace / ((3/4)^gain * screenGain) for(int i = 0; i < nbChannels; ++i){ ++gains[i]; channelFactors[i] = static_cast(alpha * pow(0.75,gains.at(i))); @@ -1568,305 +1566,283 @@ void TraceView::drawTraces( const QList& channels,bool highlight) update(); } -void TraceView::drawTraces(QPainter& painter){ - channelsStartingOrdinate.clear(); - int limit = viewportToWorldHeight(1); - int nbSamples = tracesProvider.getNbSamples(startTime,endTime,startTimeInRecordingUnits); - int nbSamplesToDraw = static_cast(floor(0.5 + static_cast(nbSamples)/downSampling)); - qDebug()<<"nbSamplesToDraw "< >::Iterator iterator; + for(iterator = selectedEvents.begin(); iterator != selectedEvents.end(); ++iterator){ + QList eventList = iterator.value(); + QString providerName = iterator.key(); + if (eventList.size() == 0 || eventsData[providerName] == 0) continue; + ItemColors* colors = providerItemColors[providerName]; + Array& currentData = static_cast(eventsData[providerName])->getTimes(); + Array& currentIds = static_cast(eventsData[providerName])->getIds(); + int nbEvents = currentData.nbOfColumns(); + for(int i = 1; i <= nbEvents;++i){ + dataType index = currentData(1,i); + int eventId = currentIds(1,i); + if (eventList.contains(eventId)){ + QColor color = colors->color(eventId); + pen.setColor(color); + pen.setCosmetic(true); + painter.setPen(pen); + int abscissa = X + static_cast(0.5 + (static_cast(index) / downSampling)); + painter.drawLine(abscissa,top,abscissa,bottom); + } + } + } + }//events +} - //Loop on all the groups (one by column) - clustersOrder.clear(); - rasterOrdinates.clear(); - rasterAbscisses.clear(); - QList groupIds = shownGroupsChannels.keys(); - QList::iterator iterator; - for(iterator = groupIds.begin(); iterator != groupIds.end(); ++iterator){ - //Draw events - if (!selectedEvents.isEmpty()){ - QRect windowRectangle((QRect)window); - int top = windowRectangle.top(); - int bottom = windowRectangle.bottom(); - QPen pen(Qt::DotLine); - pen.setCosmetic(true); +void TraceView::drawTracesMultiColumns(QPainter& painter, int limit, int nbSamples, int nbSamplesToDraw){ + //The abscissa of the system coordinate center for the current channel + int X = X0; + //The ordinate of the system coordinate center for the current channel + int Y = Y0; - QMap >::Iterator iterator; - for(iterator = selectedEvents.begin(); iterator != selectedEvents.end(); ++iterator){ - QList eventList = iterator.value(); - QString providerName = iterator.key(); - if (eventList.size() == 0 || eventsData[providerName] == 0) continue; - ItemColors* colors = providerItemColors[providerName]; - Array& currentData = static_cast(eventsData[providerName])->getTimes(); - Array& currentIds = static_cast(eventsData[providerName])->getIds(); - int nbEvents = currentData.nbOfColumns(); - for(int i = 1; i <= nbEvents;++i){ + //Start at the top of the view. + + //Loop on all the groups (one by column) + clustersOrder.clear(); + rasterOrdinates.clear(); + rasterAbscisses.clear(); + QList groupIds = shownGroupsChannels.keys(); + QList::iterator iterator; + for(iterator = groupIds.begin(); iterator != groupIds.end(); ++iterator){ + + //Draw events + drawTracesDrawEvents(painter, X, true); + + if (verticalLines && nbClusters != 0){ + QRect windowRectangle((QRect)window); + int top = windowRectangle.top(); + int bottom = windowRectangle.bottom(); + QList clusterFileList = (*groupClusterFiles)[*iterator]; + + QMap >::Iterator selectedIterator; + for(selectedIterator = selectedClusters.begin(); selectedIterator != selectedClusters.end(); ++selectedIterator){ + //Only draw vertical lines for clusters contained in a cluster file containing data for channels of the current group + if (!clusterFileList.contains(selectedIterator.key())) continue; + QString providerName = QString::number(selectedIterator.key()); + if (clustersData[providerName] == 0) + continue; + + ItemColors* colors = providerItemColors[providerName]; + Array& currentData = static_cast(clustersData[providerName])->getData(); + int nbSpikes = currentData.nbOfColumns(); + QList clusterList = selectedIterator.value(); + QList::iterator clusterIterator; + for(clusterIterator = clusterList.begin(); clusterIterator != clusterList.end(); ++clusterIterator){ + QColor color = colors->color(*clusterIterator); + QPen pen(color); + pen.setCosmetic(true); + painter.setPen(pen); + for(int i = 1; i <= nbSpikes;++i){ dataType index = currentData(1,i); - int eventId = currentIds(1,i); - if (eventList.contains(eventId)){ - QColor color = colors->color(eventId); - pen.setColor(color); - pen.setCosmetic(true); - painter.setPen(pen); + dataType clusterId = currentData(2,i); + if (clusterId == *clusterIterator){ int abscissa = X + static_cast(0.5 + (static_cast(index) / downSampling)); painter.drawLine(abscissa,top,abscissa,bottom); } } } - }//events - if (verticalLines && nbClusters != 0){ - QRect windowRectangle((QRect)window); - int top = windowRectangle.top(); - int bottom = windowRectangle.bottom(); - QList clusterFileList = (*groupClusterFiles)[*iterator]; + } + }//verticalLines - QMap >::Iterator selectedIterator; - for(selectedIterator = selectedClusters.begin(); selectedIterator != selectedClusters.end(); ++selectedIterator){ - //Only draw vertical lines for clusters contained in a cluster file containing data for channels of the current group - if (!clusterFileList.contains(selectedIterator.key())) continue; - QString providerName = QString::number(selectedIterator.key()); - if (clustersData[providerName] == 0) - continue; + QList channelIds = shownGroupsChannels[*iterator]; + int currentNbChannels = channelIds.size(); + + QList positions; + int y = Y; + for(int j = 0; j < currentNbChannels; ++j){ + const int channelId = channelIds.at(j); + + int m = 0; + if (autocenterChannels){ + for (int i = 1;i <= nbSamples;++i) m += static_cast(data(i,channelId + 1) * channelFactors.at(channelId)); + m /= nbSamples; + } + + int position = -y + m + channelOffsets.at(channelId); + positions.append(position); + channelsStartingOrdinate.insert(channelId,position - static_cast(data(1,channelId + 1) * channelFactors.at(channelId))); + channelsStartingAbscissa.insert(channelId,X); + y -= Yshift; + } - ItemColors* colors = providerItemColors[providerName]; - Array& currentData = static_cast(clustersData[providerName])->getData(); - int nbSpikes = currentData.nbOfColumns(); - QList clusterList = selectedIterator.value(); - QList::iterator clusterIterator; - for(clusterIterator = clusterList.begin(); clusterIterator != clusterList.end(); ++clusterIterator){ - QColor color = colors->color(*clusterIterator); - QPen pen(color); - pen.setCosmetic(true); - painter.setPen(pen); - for(int i = 1; i <= nbSpikes;++i){ - dataType index = currentData(1,i); - dataType clusterId = currentData(2,i); - if (clusterId == *clusterIterator){ - int abscissa = X + static_cast(0.5 + (static_cast(index) / downSampling)); - painter.drawLine(abscissa,top,abscissa,bottom); - } - } - } - } - }//verticalLines - - QList channelIds = shownGroupsChannels[*iterator]; - int currentNbChannels = channelIds.size(); - - QList positions; - int y = Y; - for(int j = 0; j < currentNbChannels; ++j){ - const int channelId = channelIds.at(j); - - int m = 0; - if (autocenterChannels){ - for (int i = 1;i <= nbSamples;++i) m += static_cast(data(i,channelId + 1) * channelFactors.at(channelId)); - m /= nbSamples; - } - - int position = -y + m + channelOffsets.at(channelId); - positions.append(position); - channelsStartingOrdinate.insert(channelId,position - static_cast(data(1,channelId + 1) * channelFactors.at(channelId))); - channelsStartingAbscissa.insert(channelId,X); - y -= Yshift; - } + for(int j = 0; j < currentNbChannels; ++j){ + const int channelId = channelIds.at(j); + //The abscissa of the system coordinate center for the current channel + int x = 0; - for(int j = 0; j < currentNbChannels; ++j){ - const int channelId = channelIds.at(j); - //The abscissa of the system coordinate center for the current channel - int x = 0; + //if the channel is skipped, do no draw it + if (skippedChannels.contains(channelId)) continue; - //if the channel is skipped, do no draw it - if (skippedChannels.contains(channelId)) continue; + QColor color = channelColors->color(channelId); - QColor color = channelColors->color(channelId); + if (greyScaleMode){ + int greyvalue = qGray(color.rgb()); + color.setHsv(0,0,greyvalue); + } + QPen pen(color,1); + if (mSelectedChannels.contains(channelId)) + pen.setWidth(2); + pen.setCosmetic(true); + painter.setPen(pen); - if (greyScaleMode){ - int greyvalue = qGray(color.rgb()); - color.setHsv(0,0,greyvalue); + if (downSampling != 1){ + drawTrace(painter,limit,positions.at(j),X,channelId,nbSamplesToDraw); + } + else{ + bool areClustersToDraw = false; + int clusterFileId = 0; + QString providerName; + if (!clusterProviders.isEmpty()){ + areClustersToDraw = true; + clusterFileId = (*channelClusterFiles)[channelId]; + providerName = QString::number(clusterFileId); + if (clustersData.contains(providerName)) + areClustersToDraw = true; } - QPen pen(color,1); - if (mSelectedChannels.contains(channelId)) - pen.setWidth(2); - pen.setCosmetic(true); - painter.setPen(pen); - if (downSampling != 1){ - drawTrace(painter,limit,positions.at(j),X,channelId,nbSamplesToDraw); + if (!waveforms || (waveforms && !areClustersToDraw) || (waveforms && areClustersToDraw && !selectedClusters.contains(clusterFileId))){ + QPolygon trace(nbSamples); + for(int i = 0; i < nbSamples;++i){ + int y = positions[j] - static_cast(data(i + 1,channelId + 1) * channelFactors[channelId]); + trace.setPoint(i,X + x,y); + x += Xstep; + } + painter.drawPolyline(trace); } else{ - bool areClustersToDraw = false; - int clusterFileId = 0; - QString providerName; - if (!clusterProviders.isEmpty()){ - areClustersToDraw = true; - clusterFileId = (*channelClusterFiles)[channelId]; - providerName = QString::number(clusterFileId); - if (clustersData.contains(providerName)) - areClustersToDraw = true; + //Array containing 3 lines: sample index, abscissa and ordinate + Array traceInfo(3,nbSamples); + QPolygon trace(nbSamples); + for(int i = 1; i <= nbSamples;++i){ + int y = positions[j] - static_cast(data(i,channelId + 1) * channelFactors[channelId]); + trace.setPoint(i - 1,X + x,y); + traceInfo(1,i) = i; + traceInfo(2,i) = X + x; + traceInfo(3,i) = y; + x += Xstep; } + painter.drawPolyline(trace); - if (!waveforms || (waveforms && !areClustersToDraw) || (waveforms && areClustersToDraw && !selectedClusters.contains(clusterFileId))){ - QPolygon trace(nbSamples); - for(int i = 0; i < nbSamples;++i){ - int y = positions[j] - static_cast(data(i + 1,channelId + 1) * channelFactors[channelId]); - trace.setPoint(i,X + x,y); - x += Xstep; - } - painter.drawPolyline(trace); - } - else{ - //Array containing 3 lines: sample index, abscissa and ordinate - Array traceInfo(3,nbSamples); - QPolygon trace(nbSamples); - for(int i = 1; i <= nbSamples;++i){ - int y = positions[j] - static_cast(data(i,channelId + 1) * channelFactors[channelId]); - trace.setPoint(i - 1,X + x,y); - traceInfo(1,i) = i; - traceInfo(2,i) = X + x; - traceInfo(3,i) = y; - x += Xstep; - } - painter.drawPolyline(trace); + //Draw the waveforms on top of the trace + ItemColors* colors = providerItemColors[providerName]; + Array& currentData = static_cast(clustersData[providerName])->getData(); + int nbSpikes = currentData.nbOfColumns(); + QList clusterList = selectedClusters[clusterFileId]; + int currentIndex = 1; - //Draw the waveforms on top of the trace - ItemColors* colors = providerItemColors[providerName]; - Array& currentData = static_cast(clustersData[providerName])->getData(); - int nbSpikes = currentData.nbOfColumns(); - QList clusterList = selectedClusters[clusterFileId]; - int currentIndex = 1; + for(int i = 1; i < nbSpikes + 1;++i){ + dataType index = currentData(1,i); + int firstIndex = qMax(1L,index - nbSamplesBefore); + int lastIndex = qMin((long)nbSamples,index + nbSamplesAfter); + int nbWaveformSamples = lastIndex - firstIndex + 1; + dataType clusterId = currentData(2,i); - for(int i = 1; i < nbSpikes + 1;++i){ - dataType index = currentData(1,i); - int firstIndex = qMax(1L,index - nbSamplesBefore); - int lastIndex = qMin((long)nbSamples,index + nbSamplesAfter); - int nbWaveformSamples = lastIndex - firstIndex + 1; - dataType clusterId = currentData(2,i); - - if (clusterList.contains(clusterId)){ - QColor color = colors->color(clusterId); - QPen pen(color,1); - if (mSelectedChannels.contains(channelId)) pen.setWidth(2); - pen.setCosmetic(true); - painter.setPen(pen); - - for(int j = currentIndex; j <= nbSamples;++j){ - if (firstIndex > traceInfo(1,j)) continue;//case 1 - else if (firstIndex == traceInfo(1,j)){//case 2 - QPolygon trace(nbWaveformSamples); - int pos = 0; - for(int k = firstIndex;k<= lastIndex;++k){ - trace.setPoint(pos,traceInfo(2,k),traceInfo(3,k)); - pos++; - } - painter.drawPolyline(trace); - currentIndex = firstIndex; - break; + if (clusterList.contains(clusterId)){ + QColor color = colors->color(clusterId); + QPen pen(color,1); + if (mSelectedChannels.contains(channelId)) pen.setWidth(2); + pen.setCosmetic(true); + painter.setPen(pen); + + for(int j = currentIndex; j <= nbSamples;++j){ + if (firstIndex > traceInfo(1,j)) continue;//case 1 + else if (firstIndex == traceInfo(1,j)){//case 2 + QPolygon trace(nbWaveformSamples); + int pos = 0; + for(int k = firstIndex;k<= lastIndex;++k){ + trace.setPoint(pos,traceInfo(2,k),traceInfo(3,k)); + pos++; } - }//loop on samples to draw - } - }//loop on spikes - }//else waveform - } + painter.drawPolyline(trace); + currentIndex = firstIndex; + break; + } + }//loop on samples to draw + } + }//loop on spikes + }//else waveform } + } - //Loop on all the selected clusters (first on the cluster files containing selected clusters) if the raster is asked. - if (raster && nbClusters != 0){ - int y = Y0Raster; + //Loop on all the selected clusters (first on the cluster files containing selected clusters) if the raster is asked. + if (raster && nbClusters != 0){ + int y = Y0Raster; - QList clusterFileList = (*groupClusterFiles)[*iterator]; + QList clusterFileList = (*groupClusterFiles)[*iterator]; - QMap >::Iterator selectedIterator; - for(selectedIterator = selectedClusters.begin(); selectedIterator != selectedClusters.end(); ++selectedIterator){ - //Only draw rasters for clusters contained in a cluster file containing data for channels of the current group - if (!clusterFileList.contains(selectedIterator.key())) continue; - QList clusterList = selectedIterator.value(); - if (clusterList.size() == 0) continue; - QString providerName = QString::number(selectedIterator.key()); - ItemColors* colors = providerItemColors[providerName]; - Array& currentData = static_cast(clustersData[providerName])->getData(); - int nbSpikes = currentData.nbOfColumns(); - QList::iterator clusterIterator; - for(clusterIterator = clusterList.begin(); clusterIterator != clusterList.end(); ++clusterIterator){ - const QString identifier = QString::fromLatin1("%1-%2").arg(providerName).arg(*clusterIterator); + QMap >::Iterator selectedIterator; + for(selectedIterator = selectedClusters.begin(); selectedIterator != selectedClusters.end(); ++selectedIterator){ + //Only draw rasters for clusters contained in a cluster file containing data for channels of the current group + if (!clusterFileList.contains(selectedIterator.key())) continue; + QList clusterList = selectedIterator.value(); + if (clusterList.size() == 0) continue; + QString providerName = QString::number(selectedIterator.key()); + ItemColors* colors = providerItemColors[providerName]; + Array& currentData = static_cast(clustersData[providerName])->getData(); + int nbSpikes = currentData.nbOfColumns(); + QList::iterator clusterIterator; + for(clusterIterator = clusterList.begin(); clusterIterator != clusterList.end(); ++clusterIterator){ + const QString identifier = QString::fromLatin1("%1-%2").arg(providerName).arg(*clusterIterator); - clustersOrder.append(identifier); - rasterOrdinates.append(-y); - rasterAbscisses.append(X); - QColor color = colors->color(*clusterIterator); - QPen pen(color); - pen.setCosmetic(true); - painter.setPen(pen); - int bottom = y - rasterHeight; - for(int i = 1; i <= nbSpikes;++i){ - dataType index = currentData(1,i); - dataType clusterId = currentData(2,i); - if (clusterId == *clusterIterator){ - int abscissa = X + static_cast(0.5 + (static_cast(index) / downSampling)); - painter.drawLine(abscissa,-y,abscissa,-bottom); - } + clustersOrder.append(identifier); + rasterOrdinates.append(-y); + rasterAbscisses.append(X); + QColor color = colors->color(*clusterIterator); + QPen pen(color); + pen.setCosmetic(true); + painter.setPen(pen); + int bottom = y - rasterHeight; + for(int i = 1; i <= nbSpikes;++i){ + dataType index = currentData(1,i); + dataType clusterId = currentData(2,i); + if (clusterId == *clusterIterator){ + int abscissa = X + static_cast(0.5 + (static_cast(index) / downSampling)); + painter.drawLine(abscissa,-y,abscissa,-bottom); } - y -= (rasterHeight + YRasterSpace); - } - } - }//raster - X += Xshift; - }//groups (<=> columns) - }//multicolumns - //traces presented on a single column - else{ - //Draw events - if (!selectedEvents.isEmpty()){ - QRect windowRectangle((QRect)window); - int top = windowRectangle.top(); - int bottom = windowRectangle.bottom(); - QPen pen(Qt::DotLine); - QMap >::Iterator iterator; - for(iterator = selectedEvents.begin(); iterator != selectedEvents.end(); ++iterator){ - QList eventList = iterator.value(); - QString providerName = iterator.key(); - - if (eventList.size() == 0 || eventsData[providerName] == 0) continue; - ItemColors* colors = providerItemColors[providerName]; - Array& currentData = static_cast(eventsData[providerName])->getTimes(); - Array& currentIds = static_cast(eventsData[providerName])->getIds(); - int nbEvents = currentData.nbOfColumns(); - for(int i = 1; i <= nbEvents;++i){ - dataType index = currentData(1,i); - int eventId = currentIds(1,i); - if (eventList.contains(eventId)){ - QColor color = colors->color(eventId); - pen.setColor(color); - pen.setCosmetic(true); - painter.setPen(pen); - int abscissa = static_cast(0.5 + (static_cast(index) / downSampling)); - painter.drawLine(abscissa,top,abscissa,bottom); } + y -= (rasterHeight + YRasterSpace); } } - }//events + }//raster + X += Xshift; + }//groups (<=> columns) + +} + +void TraceView::drawTracesSingleColumn(QPainter& painter, int limit, int nbSamples, int nbSamplesToDraw){ + //Draw events + drawTracesDrawEvents(painter, 0, false); + + //Draw clusters on vertical lines + if (verticalLines && nbClusters != 0){ + QRect windowRectangle((QRect)window); + int top = windowRectangle.top(); + int bottom = windowRectangle.bottom(); + QHashIterator iterator(clustersData); + while (iterator.hasNext()) { + iterator.next(); + ItemColors* colors = providerItemColors[iterator.key()]; + QList clusterList = selectedClusters[iterator.key().toInt()]; + if (iterator.value() != 0) + { - //Draw clusters on vertical lines - if (verticalLines && nbClusters != 0){ - QRect windowRectangle((QRect)window); - int top = windowRectangle.top(); - int bottom = windowRectangle.bottom(); - QHashIterator iterator(clustersData); - while (iterator.hasNext()) { - iterator.next(); - ItemColors* colors = providerItemColors[iterator.key()]; - QList clusterList = selectedClusters[iterator.key().toInt()]; Array& currentData = iterator.value()->getData(); + int nbSpikes = currentData.nbOfColumns(); for(int i = 1; i < nbSpikes + 1;++i){ @@ -1883,193 +1859,212 @@ void TraceView::drawTraces(QPainter& painter){ } } } - }//verticalLines + } + }//verticalLines - //The ordinate of the system coordinate center for the current channel - int Y = Y0; - //Start at the top of the view. + //The ordinate of the system coordinate center for the current channel + int Y = Y0; + //Start at the top of the view. - //Loop on all the groups - QList groupIds = shownGroupsChannels.keys(); - QList::iterator iterator; - for(iterator = groupIds.begin(); iterator != groupIds.end(); ++iterator){ - QList channelIds = shownGroupsChannels[*iterator]; - int currentNbChannels = channelIds.size(); - - QList positions; - int y = Y; - for(int j = 0; j < currentNbChannels; ++j){ - int channelId = channelIds[j]; - - int m = 0; - if (autocenterChannels){ - for (int i = 1;i <= nbSamples;++i) m += static_cast(data(i,channelId + 1) * channelFactors.at(channelId)); - m /= nbSamples; - } - - int position = -y + m + channelOffsets[channelId]; - positions.append(position); - channelsStartingOrdinate.insert(channelId,position - static_cast(data(1,channelId + 1) * channelFactors[channelId])); - channelsStartingAbscissa.insert(channelId,X0); - y -= Yshift; - } + //Loop on all the groups + QList groupIds = shownGroupsChannels.keys(); + QList::iterator iterator; + for(iterator = groupIds.begin(); iterator != groupIds.end(); ++iterator){ + QList channelIds = shownGroupsChannels[*iterator]; + int currentNbChannels = channelIds.size(); + + QList positions; + int y = Y; + for(int j = 0; j < currentNbChannels; ++j){ + int channelId = channelIds[j]; + + int m = 0; + if (autocenterChannels){ + for (int i = 1;i <= nbSamples;++i) m += static_cast(data(i,channelId + 1) * channelFactors.at(channelId)); + m /= nbSamples; + } + + int position = -y + m + channelOffsets[channelId]; + positions.append(position); + channelsStartingOrdinate.insert(channelId,position - static_cast(data(1,channelId + 1) * channelFactors[channelId])); + channelsStartingAbscissa.insert(channelId,X0); + y -= Yshift; + } - for(int j = 0; j < currentNbChannels; ++j){ - int channelId = channelIds[j]; + for(int j = 0; j < currentNbChannels; ++j){ + int channelId = channelIds[j]; - //if the channel is skipped, do no draw it - if (skippedChannels.contains(channelId)) continue; + //if the channel is skipped, do no draw it + if (skippedChannels.contains(channelId)) continue; - //The abscissa of the system coordinate center for the current channel - int X = X0; + //The abscissa of the system coordinate center for the current channel + int X = X0; - //Get the color associated with the channel and set the color to use to this color - QColor color = channelColors->color(channelId); - if (greyScaleMode){ - int greyvalue = qGray(color.rgb()); - color.setHsv(0,0,greyvalue); + //Get the color associated with the channel and set the color to use to this color + QColor color = channelColors->color(channelId); + if (greyScaleMode){ + int greyvalue = qGray(color.rgb()); + color.setHsv(0,0,greyvalue); + } + QPen pen(color,1); + if (mSelectedChannels.contains(channelId)) pen.setWidth(2); + pen.setCosmetic(true); + painter.setPen(pen); + + if (downSampling != 1){ + drawTrace(painter,limit,positions[j],X,channelId,nbSamplesToDraw); + } + else{ + bool areClustersToDraw = false; + int clusterFileId = 0; + QString providerName; + + if (!clusterProviders.isEmpty()){ + areClustersToDraw = true; + clusterFileId = (*channelClusterFiles)[channelId]; + providerName = QString::number(clusterFileId); + if (clustersData.contains(providerName)) + areClustersToDraw = true; } - QPen pen(color,1); - if (mSelectedChannels.contains(channelId)) pen.setWidth(2); - pen.setCosmetic(true); - painter.setPen(pen); - if (downSampling != 1){ - drawTrace(painter,limit,positions[j],X,channelId,nbSamplesToDraw); + if (!waveforms || (waveforms && !areClustersToDraw) || (waveforms && areClustersToDraw && !selectedClusters.contains(clusterFileId))){ + QPolygon trace(nbSamples); + for(int i = 0; i < nbSamples;++i){ + int y = positions[j] - static_cast(data(i + 1,channelId + 1) * channelFactors[channelId]); + long d = data(i + 1,channelId + 1); + float cf = channelFactors[channelId]; + int pj = positions[j]; + trace.setPoint(i,X,y); + X += Xstep; + } + painter.drawPolyline(trace); } else{ - bool areClustersToDraw = false; - int clusterFileId = 0; - QString providerName; - - if (!clusterProviders.isEmpty()){ - areClustersToDraw = true; - clusterFileId = (*channelClusterFiles)[channelId]; - providerName = QString::number(clusterFileId); - if (clustersData.contains(providerName)) - areClustersToDraw = true; + //Array containing 3 lines: sample index, abscissa and ordinate + Array traceInfo(3,nbSamples); + QPolygon trace(nbSamples); + for(int i = 1; i <= nbSamples;++i){ + int y = positions[j] - static_cast(data(i,channelId + 1) * channelFactors[channelId]); + trace.setPoint(i - 1,X,y); + traceInfo(1,i) = i; + traceInfo(2,i) = X; + traceInfo(3,i) = y; + X += Xstep; } + painter.drawPolyline(trace); - if (!waveforms || (waveforms && !areClustersToDraw) || (waveforms && areClustersToDraw && !selectedClusters.contains(clusterFileId))){ - QPolygon trace(nbSamples); - for(int i = 0; i < nbSamples;++i){ - int y = positions[j] - static_cast(data(i + 1,channelId + 1) * channelFactors[channelId]); - trace.setPoint(i,X,y); - X += Xstep; - } - painter.drawPolyline(trace); - } - else{ - //Array containing 3 lines: sample index, abscissa and ordinate - Array traceInfo(3,nbSamples); - QPolygon trace(nbSamples); - for(int i = 1; i <= nbSamples;++i){ - int y = positions[j] - static_cast(data(i,channelId + 1) * channelFactors[channelId]); - trace.setPoint(i - 1,X,y); - traceInfo(1,i) = i; - traceInfo(2,i) = X; - traceInfo(3,i) = y; - X += Xstep; - } - painter.drawPolyline(trace); + //Draw the waveforms on top of the trace + ItemColors* colors = providerItemColors[providerName]; - //Draw the waveforms on top of the trace - ItemColors* colors = providerItemColors[providerName]; + Array& currentData = static_cast(clustersData[providerName])->getData(); + int nbSpikes = currentData.nbOfColumns(); + QList clusterList = selectedClusters[clusterFileId]; + int currentIndex = 1; - Array& currentData = static_cast(clustersData[providerName])->getData(); - int nbSpikes = currentData.nbOfColumns(); - QList clusterList = selectedClusters[clusterFileId]; - int currentIndex = 1; + for(int i = 1; i < nbSpikes + 1;++i){ + dataType index = currentData(1,i); + int firstIndex = qMax(1L,index - nbSamplesBefore); + int lastIndex = qMin((long)nbSamples,index + nbSamplesAfter); + int nbWaveformSamples = lastIndex - firstIndex + 1; + dataType clusterId = currentData(2,i); - for(int i = 1; i < nbSpikes + 1;++i){ - dataType index = currentData(1,i); - int firstIndex = qMax(1L,index - nbSamplesBefore); - int lastIndex = qMin((long)nbSamples,index + nbSamplesAfter); - int nbWaveformSamples = lastIndex - firstIndex + 1; - dataType clusterId = currentData(2,i); - - if (clusterList.contains(clusterId)){ - QColor color = colors->color(clusterId); - QPen pen(color,1); - - if (mSelectedChannels.contains(channelId)) - pen.setWidth(2); - pen.setCosmetic(true); - painter.setPen(pen); - - for(int j = currentIndex; j <= nbSamples;++j){ - if (firstIndex > traceInfo(1,j)) continue;//case 1 - else if (firstIndex == traceInfo(1,j)){//case 2 - QPolygon trace(nbWaveformSamples); - int pos = 0; - for(int k = firstIndex;k<= lastIndex;++k){ - trace.setPoint(pos,traceInfo(2,k),traceInfo(3,k)); - pos++; - } - painter.drawPolyline(trace); - currentIndex = firstIndex; - break; - } - }//loop on samples to draw - } - }//loop on spikes - }//else waveform - } + if (clusterList.contains(clusterId)){ + QColor color = colors->color(clusterId); + QPen pen(color,1); - } + if (mSelectedChannels.contains(channelId)) + pen.setWidth(2); + pen.setCosmetic(true); + painter.setPen(pen); - Y -= (currentNbChannels * traceVspace + (currentNbChannels -1) * Yspace); - Y -= YGroupSpace; - }//groups + for(int j = currentIndex; j <= nbSamples;++j){ + if (firstIndex > traceInfo(1,j)) continue;//case 1 + else if (firstIndex == traceInfo(1,j)){//case 2 + QPolygon trace(nbWaveformSamples); + int pos = 0; + for(int k = firstIndex;k<= lastIndex;++k){ + trace.setPoint(pos,traceInfo(2,k),traceInfo(3,k)); + pos++; + } + painter.drawPolyline(trace); + currentIndex = firstIndex; + break; + } + }//loop on samples to draw + } + }//loop on spikes + }//else waveform + } - clustersOrder.clear(); - rasterOrdinates.clear(); - rasterAbscisses.clear(); + } - //Loop on all the selected clusters (first on the cluster files containing selected clusters) if the raster is asked. - if (raster && nbClusters != 0){ - Y = Y0Raster; + Y -= (currentNbChannels * traceVspace + (currentNbChannels -1) * Yspace); + Y -= YGroupSpace; + }//groups - QMap >::Iterator iterator; - for(iterator = selectedClusters.begin(); iterator != selectedClusters.end(); ++iterator){ - qDebug()<<" selectedClusters.begin()"<<*iterator; - QList clusterList = iterator.value(); - if (clusterList.isEmpty()) - continue; - QString providerName = QString::number(iterator.key()); - qDebug()<<" providerName "<& currentData = static_cast(clustersData[providerName])->getData(); - int nbSpikes = currentData.nbOfColumns(); - QList::iterator clusterIterator; - QList::iterator clusterIteratorEnd(clusterList.end()); - for(clusterIterator = clusterList.begin(); clusterIterator != clusterIteratorEnd; ++clusterIterator){ - const QString identifier = QString("%1-%2").arg(providerName).arg(*clusterIterator); + clustersOrder.clear(); + rasterOrdinates.clear(); + rasterAbscisses.clear(); - qDebug()<<" *** identifier " <color(*clusterIterator); - QPen pen(color); - pen.setCosmetic(true); - painter.setPen(pen); - int bottom = Y - rasterHeight; - for(int i = 1; i < nbSpikes + 1;++i){ - dataType index = currentData(1,i); - dataType clusterId = currentData(2,i); - if (clusterId == *clusterIterator){ - int abscissa = static_cast(0.5 + (static_cast(index) / downSampling)); - painter.drawLine(abscissa,-Y,abscissa,-bottom); - } + QMap >::Iterator iterator; + for(iterator = selectedClusters.begin(); iterator != selectedClusters.end(); ++iterator){ + qDebug()<<" selectedClusters.begin()"<<*iterator; + QList clusterList = iterator.value(); + if (clusterList.isEmpty()) + continue; + QString providerName = QString::number(iterator.key()); + qDebug()<<" providerName "<& currentData = static_cast(clustersData[providerName])->getData(); + int nbSpikes = currentData.nbOfColumns(); + QList::iterator clusterIterator; + QList::iterator clusterIteratorEnd(clusterList.end()); + for(clusterIterator = clusterList.begin(); clusterIterator != clusterIteratorEnd; ++clusterIterator){ + const QString identifier = QString("%1-%2").arg(providerName).arg(*clusterIterator); + + qDebug()<<" *** identifier " <color(*clusterIterator); + QPen pen(color); + pen.setCosmetic(true); + painter.setPen(pen); + int bottom = Y - rasterHeight; + for(int i = 1; i < nbSpikes + 1;++i){ + dataType index = currentData(1,i); + dataType clusterId = currentData(2,i); + if (clusterId == *clusterIterator){ + int abscissa = static_cast(0.5 + (static_cast(index) / downSampling)); + painter.drawLine(abscissa,-Y,abscissa,-bottom); } - Y -= (rasterHeight + YRasterSpace); } + Y -= (rasterHeight + YRasterSpace); } - }//raster + } + }//raster +} + +void TraceView::drawTraces(QPainter& painter){ + channelsStartingOrdinate.clear(); + int limit = viewportToWorldHeight(1); + int nbSamples = tracesProvider.getNbSamples(startTime,endTime,startTimeInRecordingUnits); + int nbSamplesToDraw = static_cast(floor(0.5 + static_cast(nbSamples)/downSampling)); + + //traces presented on multiple columns + if (multiColumns){ + drawTracesMultiColumns(painter, limit, nbSamples, nbSamplesToDraw); + }//multicolumns + //traces presented on a single column + else{ + drawTracesSingleColumn(painter, limit, nbSamples, nbSamplesToDraw); }//single column } @@ -2319,7 +2314,7 @@ void TraceView::mouseMoveEvent(QMouseEvent* event){ int delta = current.y() - lastClickOrdinate; - float voltage = (delta/channelFactors[channelforVoltageComputation])/acquisitionGain; + float voltage = (delta/channelFactors[channelforVoltageComputation]) / 1000.0f; message.append(tr(" Voltage: %1").arg(QString("%1 mV").arg(fabs(voltage),0,'f',2))); } else{ @@ -3338,7 +3333,7 @@ void TraceView::reset(){ for(int i = 0; i < nbChannels; ++i) channelOffsets.append(0); //Set the initial amplitude and factor for each channel. - setGains(unitGain,acquisitionGain); + setGains(screenGain); //Get the data. // tracesProvider.requestData(startTime,endTime,this,startTimeInRecordingUnits); @@ -3351,8 +3346,7 @@ void TraceView::drawCalibrationScale(QPainter& painter){ painter.setPen(colorLegend); //set the color for the legends. //Calibration scale, is meaningful only if all the channels have the same amplification. Take the channel 0 as the provider - int nbRU = static_cast(unitGain * channelFactors[0]); - float screenGain = static_cast(static_cast(unitGain)/static_cast(acquisitionGain)); + int nbRU = static_cast(screenGain * 1000 * channelFactors[0]); float gain = channelDisplayGains[0]; QFontInfo fontInfo = QFontInfo(f); @@ -3741,7 +3735,7 @@ void TraceView::removeClusterProvider(const QString &name, bool active){ void TraceView::showClusters(const QString &name, const QList &clustersToShow){ - qDebug()<<" void TraceView::showClusters(const QString &name, const QList &clustersToShow){"< &clustersToShow){"< clusters; diff --git a/src/traceview.h b/src/traceview.h index 0fbe714..70ddfb2 100644 --- a/src/traceview.h +++ b/src/traceview.h @@ -63,8 +63,7 @@ class TraceView : public BaseFrame { * @param waveforms true if waveforms are drawn on top of the traces, false otherwise. * @param labelsDisplay true if labels are drawn next to the traces, false otherwise. * @param channelsToDisplay a reference on the list of channel to be shown at the opening of the view. - * @param unitGain initial gain use to draw the traces. - * @param acquisitionGain acquisition gain. + * @param screenGain initial gain use to draw the traces. * @param start starting time in miliseconds. * @param timeFrameWidth time window in miliseconds. * @param channelColors a pointer on the list of colors for the channels. @@ -89,7 +88,7 @@ class TraceView : public BaseFrame { * @param border size of the border between the frame and the contents. */ TraceView(TracesProvider& tracesProvider, bool greyScale, bool multiColumns, bool verticalLines, - bool raster, bool waveforms, bool labelsDisplay, QList& channelsToDisplay, int unitGain, int acquisitionGain, long start, long timeFrameWidth, + bool raster, bool waveforms, bool labelsDisplay, QList& channelsToDisplay, float screenGain, long start, long timeFrameWidth, ChannelColors* channelColors, QMap >* groupsChannels, QMap* channelsGroups, bool autocenterChannels, QList& channelOffsets, QList& gains, const QList& skippedChannels, int rasterHeight, const QImage &backgroundImage, QWidget* parent=0, const char* name=0, const QColor& backgroundColor = Qt::black, QStatusBar* statusBar = 0L, int minSize = 500, int maxSize = 4000, int windowTopLeft = -500, int windowBottomRight = 1001, int border = 0); @@ -100,7 +99,10 @@ class TraceView : public BaseFrame { /// Added by M.Zugaro to enable automatic forward paging qlonglong recordingLength() const { return length; } - void updateRecordingLength() { tracesProvider.updateRecordingLength();length = tracesProvider.recordingLength(); } + void updateRecordingLength() { + tracesProvider.updateRecordingLength(); + length = tracesProvider.recordingLength(); + } /**Enum to be use as a Mode. *
    @@ -167,11 +169,10 @@ class TraceView : public BaseFrame { */ void decreaseSelectedChannelsAmplitude(const QList& channelIds); - /**Sets the unit gain and the acquisition system gain. + /**Sets the gains for each channels. * @param gain initial gain use to draw the traces in the TraceView. - * @param acquisitionGain acquisition gain. */ - void setGains(int gain,int acquisitionGain); + void setGains(float gain); /**Changes the color of a channel. * @param channelId id of the channel to redraw. @@ -507,6 +508,9 @@ qDebug()<<" eventToAddProperties***********************"; void eventAdded(QString providerName,QString addedEventDescription,double time); void eventsAvailable(QHash& eventsData,QMap >& selectedEvents,QHash& providerItemColors,QObject* initiator,double samplingRate); + // Emitted if dataAvailable(...) receives faulty data + void dataError(); + protected: /** * Draws the contents of the frame @@ -543,6 +547,14 @@ qDebug()<<" eventToAddProperties***********************"; private: + void drawTracesMultiColumns(QPainter& painter, int limit, int nbSamples, int nbSamplesToDraw); + void drawTracesSingleColumn(QPainter& painter, int limit, int nbSamples, int nbSamplesToDraw); + void drawTracesDrawEvents(QPainter& painter, int X, bool bIsMulti); + + + // Amplitude maximal of theta in microvolts, e.g. 400 uV + static const float U_THETA; + /**True if the the colors are in grey-scale*/ bool greyScaleMode; @@ -595,7 +607,7 @@ qDebug()<<" eventToAddProperties***********************"; /**Number of channels used to record the data.*/ int nbChannels; - /**Position of the peak among the points decribing waveforms.*/ + /**Position of the peak among the points describing waveforms.*/ int peakPositionInWaveform; /**Number of points used to describe a waveform.*/ @@ -695,15 +707,12 @@ qDebug()<<" eventToAddProperties***********************"; /**Time in milisseconds corresponding to a step between two samples.*/ float timeStepUnit; - /**Acquisition system gain in recording unit by milivolts.*/ - int acquisitionGain; - - /**Unit gain in recording unit by centimeters.*/ - int unitGain; + /**Unit gain in mV by centimeters.*/ + float screenGain; - /**Factor, in pixels by recording units, to convert the data in recording units to data + /**Factor, in pixels by uV, to convert the data in uV to data * in pixels of the world (Yworld = alpha.factor.Ydata). - * This factor is computed using the amplitude maximal of theta and the acquisition system gain. + * This factor is computed using the amplitude maximal of theta U_THETA. */ float alpha; diff --git a/src/tracewidget.cpp b/src/tracewidget.cpp index 6f924ee..e3b9f17 100644 --- a/src/tracewidget.cpp +++ b/src/tracewidget.cpp @@ -32,21 +32,22 @@ #include TraceWidget::TraceWidget(long startTime,long duration,bool greyScale,TracesProvider& tracesProvider,bool multiColumns,bool verticalLines, - bool raster,bool waveforms,bool labelsDisplay,QList& channelsToDisplay,int gain,int acquisitionGain, - ChannelColors* channelColors,QMap >* groupsChannels, + bool raster,bool waveforms,bool labelsDisplay,QList& channelsToDisplay, float gain, ChannelColors* channelColors, QMap >* groupsChannels, QMap* channelsGroups,bool autocenterChannels,QList& channelOffsets,QList& gains,const QList& skippedChannels, int rasterHeight,const QImage& backgroundImage,QWidget* parent, const char* name,const QColor& backgroundColor,QStatusBar* statusBar, int minSize,int maxSize,int windowTopLeft,int windowBottomRight,int border): QWidget(parent),timeWindow(duration), - view(tracesProvider,greyScale,multiColumns,verticalLines,raster,waveforms,labelsDisplay,channelsToDisplay,gain,acquisitionGain, + view(tracesProvider,greyScale,multiColumns,verticalLines,raster,waveforms,labelsDisplay,channelsToDisplay,gain, startTime,timeWindow,channelColors,groupsChannels,channelsGroups,autocenterChannels,channelOffsets,gains,skippedChannels,rasterHeight,backgroundImage,this,name, backgroundColor,statusBar,minSize,maxSize,windowTopLeft,windowBottomRight,border), startTime(startTime), validator(this), isInit(true), updateView(true), - statusBar(statusBar) + statusBar(statusBar), + timer(new QTimer(this)), + pageTime(500) { QVBoxLayout *lay = new QVBoxLayout; @@ -73,11 +74,20 @@ TraceWidget::TraceWidget(long startTime,long duration,bool greyScale,TracesProvi connect(&view,SIGNAL(eventAdded(QString,QString,double)),this, SLOT(slotEventAdded(QString,QString,double))); connect(&view,SIGNAL(eventsAvailable(QHash&,QMap >&,QHash&,QObject*,double)),this, SLOT(slotEventsAvailable(QHash&,QMap >&,QHash&,QObject*,double))); + // Inform trace provider about paging changes + connect(this, SIGNAL(pagingStarted()), + &tracesProvider, SLOT(slotPagingStarted())); + connect(this, SIGNAL(pagingStopped()), + &tracesProvider, SLOT(slotPagingStopped())); + + // Stop paging if IO error occures + connect(&view, SIGNAL(dataError()), + this, SLOT(stop())); + isInit = false; - /// Added by M.Zugaro to enable automatic forward paging - timer = new QTimer(this); + + // Configure auto advance timer connect(timer,SIGNAL(timeout()),this,SLOT(advance())); - pageTime = 500; } TraceWidget::~TraceWidget(){ @@ -86,32 +96,34 @@ TraceWidget::~TraceWidget(){ /// Added by M.Zugaro to enable automatic forward paging void TraceWidget::page() { - if ( timer->isActive() ) - timer->stop(); - else - { + if(!isStill()) { + // RHM !!!! should we add this here? timer->stop(); + //timer->stop(); + return; + } + timer->start(pageTime); + emit pagingStarted(); statusBar->showMessage(tr("Auto-advance every %1 ms").arg(pageTime)); } -} bool TraceWidget::isStill() { - return ! ( timer != NULL && timer->isActive() ); + return !timer->isActive(); } void TraceWidget::stop() { - if ( timer->isActive() ) - { + if (isStill()) + return; + timer->stop(); - emit stopped(); + emit pagingStopped(); } -} void TraceWidget::accelerate() { - if ( !timer->isActive() ) + if (isStill()) return; pageTime -= 125; if ( pageTime < 0 ) pageTime = 0; @@ -121,7 +133,9 @@ void TraceWidget::accelerate() void TraceWidget::decelerate() { - if ( !timer->isActive() ) return; + if (isStill()) + return; + pageTime += 125; if ( pageTime > 1000 ) pageTime = 1000; statusBar->showMessage(tr("Auto-advance every %1 ms").arg(pageTime)); diff --git a/src/tracewidget.h b/src/tracewidget.h index 792194a..49d5a52 100644 --- a/src/tracewidget.h +++ b/src/tracewidget.h @@ -64,7 +64,6 @@ class TraceWidget : public QWidget { * @param labelsDisplay true if labels are drawn next to the traces, false otherwise. * @param channelsToDisplay a reference on the list of channel to be shown at the opening of the view. * @param gain initial gain use to draw the traces in the TraceView. - * @param acquisitionGain acquisition gain. * @param channelColors a pointer on the list of colors for the channels. * @param groupsChannels a pointer on the map given the list of channels for each group. * @param channelsGroups a pointer on the map given to which group each channel belongs. @@ -87,7 +86,7 @@ class TraceWidget : public QWidget { * @param border size of the border between the frame and the contents. */ TraceWidget(long startTime, long duration, bool greyScale, TracesProvider& tracesProvider, bool multiColumns, bool verticalLines, - bool raster, bool waveforms, bool labelsDisplay, QList& channelsToDisplay, int gain, int acquisitionGain, ChannelColors* channelColors, + bool raster, bool waveforms, bool labelsDisplay, QList& channelsToDisplay, float gain, ChannelColors* channelColors, QMap >* groupsChannels, QMap* channelsGroups, bool autocenterChannels, QList& channelOffsets, QList& gains, const QList& skippedChannels, int rasterHeight, const QImage &backgroundImage, QWidget* parent=0, const char* name=0, const QColor &backgroundColor = Qt::black, QStatusBar* statusBar = 0L, int minSize = 0, int maxSize = 4000, int windowTopLeft = -500, @@ -118,9 +117,12 @@ class TraceWidget : public QWidget { bool isStill(); Q_SIGNALS: - - /// Added by M.Zugaro to enable automatic forward paging - void stopped(); + + /** Emitted when paging in started */ + void pagingStarted(); + + /** Emitted when paging in stopped */ + void pagingStopped(); public Q_SLOTS: @@ -250,11 +252,10 @@ public Q_SLOTS: */ void decreaseSelectedChannelsAmplitude(const QList& channelIds){view.decreaseSelectedChannelsAmplitude(channelIds);} - /**Sets the unit gain and the acquisition system gain. + /**Sets the unit gain. * @param gain initial gain use to draw the traces in the TraceView. - * @param acquisitionGain acquisition gain. */ - void setGains(int gain,int acquisitionGain){view.setGains(gain,acquisitionGain);} + void setGain(float gain){view.setGains(gain);} /**Update the information presented in the view if need it.*/ void updateDrawing(){view.updateDrawing();}